| author | |
| committer | |
| log | 1e785409bb77a4ae1c8a496acae64d94c34431b9 |
| tree | 2d1c00e45239ed55181359aa2d3962fb71b2cf67 |
| parent | 028a9a10549e59e84105515ffb50c491f487c91a |
4 files changed, 452 insertions(+), 452 deletions(-)
CMakeLists.txt+1-1| ... | @@ -601,7 +601,6 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -601,7 +601,6 @@ set(ZIG_STAGE2_SOURCES |
| 601 | src/link/Elf/Archive.zig | 601 | src/link/Elf/Archive.zig |
| 602 | src/link/Elf/Atom.zig | 602 | src/link/Elf/Atom.zig |
| 603 | src/link/Elf/AtomList.zig | 603 | src/link/Elf/AtomList.zig |
| 604 | src/link/Elf/LdScript.zig | ||
| 605 | src/link/Elf/LinkerDefined.zig | 604 | src/link/Elf/LinkerDefined.zig |
| 606 | src/link/Elf/Merge.zig | 605 | src/link/Elf/Merge.zig |
| 607 | src/link/Elf/Object.zig | 606 | src/link/Elf/Object.zig |
| ... | @@ -615,6 +614,7 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -615,6 +614,7 @@ set(ZIG_STAGE2_SOURCES |
| 615 | src/link/Elf/relocatable.zig | 614 | src/link/Elf/relocatable.zig |
| 616 | src/link/Elf/relocation.zig | 615 | src/link/Elf/relocation.zig |
| 617 | src/link/Elf/synthetic_sections.zig | 616 | src/link/Elf/synthetic_sections.zig |
| 617 | src/link/LdScript.zig | ||
| 618 | src/link/MachO.zig | 618 | src/link/MachO.zig |
| 619 | src/link/MachO/Archive.zig | 619 | src/link/MachO/Archive.zig |
| 620 | src/link/MachO/Atom.zig | 620 | src/link/MachO/Atom.zig |
src/link/Elf.zig+1-1| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | pub const Atom = @import("Elf/Atom.zig"); | 1 | pub const Atom = @import("Elf/Atom.zig"); |
| 2 | pub const LdScript = @import("LdScript.zig"); | ||
| 2 | 3 | ||
| 3 | base: link.File, | 4 | base: link.File, |
| 4 | rpath_table: std.StringArrayHashMapUnmanaged(void), | 5 | rpath_table: std.StringArrayHashMapUnmanaged(void), |
| ... | @@ -5625,7 +5626,6 @@ const GnuHashSection = synthetic_sections.GnuHashSection; | ... | @@ -5625,7 +5626,6 @@ const GnuHashSection = synthetic_sections.GnuHashSection; |
| 5625 | const GotSection = synthetic_sections.GotSection; | 5626 | const GotSection = synthetic_sections.GotSection; |
| 5626 | const GotPltSection = synthetic_sections.GotPltSection; | 5627 | const GotPltSection = synthetic_sections.GotPltSection; |
| 5627 | const HashSection = synthetic_sections.HashSection; | 5628 | const HashSection = synthetic_sections.HashSection; |
| 5628 | const LdScript = @import("Elf/LdScript.zig"); | ||
| 5629 | const LinkerDefined = @import("Elf/LinkerDefined.zig"); | 5629 | const LinkerDefined = @import("Elf/LinkerDefined.zig"); |
| 5630 | const Liveness = @import("../Liveness.zig"); | 5630 | const Liveness = @import("../Liveness.zig"); |
| 5631 | const LlvmObject = @import("../codegen/llvm.zig").Object; | 5631 | const LlvmObject = @import("../codegen/llvm.zig").Object; |
src/link/Elf/LdScript.zig deleted-450| ... | @@ -1,450 +0,0 @@ | ||
| 1 | path: Path, | ||
| 2 | cpu_arch: ?std.Target.Cpu.Arch, | ||
| 3 | args: []const Arg, | ||
| 4 | |||
| 5 | pub const Arg = struct { | ||
| 6 | needed: bool = false, | ||
| 7 | path: []const u8, | ||
| 8 | }; | ||
| 9 | |||
| 10 | pub fn deinit(ls: *LdScript, gpa: Allocator) void { | ||
| 11 | gpa.free(ls.args); | ||
| 12 | ls.* = undefined; | ||
| 13 | } | ||
| 14 | |||
| 15 | pub const Error = error{ | ||
| 16 | LinkFailure, | ||
| 17 | UnexpectedToken, | ||
| 18 | UnknownCpuArch, | ||
| 19 | OutOfMemory, | ||
| 20 | }; | ||
| 21 | |||
| 22 | pub fn parse( | ||
| 23 | gpa: Allocator, | ||
| 24 | diags: *Diags, | ||
| 25 | /// For error reporting. | ||
| 26 | path: Path, | ||
| 27 | data: []const u8, | ||
| 28 | ) Error!LdScript { | ||
| 29 | var tokenizer = Tokenizer{ .source = data }; | ||
| 30 | var tokens: std.ArrayListUnmanaged(Token) = .empty; | ||
| 31 | defer tokens.deinit(gpa); | ||
| 32 | var line_col: std.ArrayListUnmanaged(LineColumn) = .empty; | ||
| 33 | defer line_col.deinit(gpa); | ||
| 34 | |||
| 35 | var line: usize = 0; | ||
| 36 | var prev_line_last_col: usize = 0; | ||
| 37 | |||
| 38 | while (true) { | ||
| 39 | const tok = tokenizer.next(); | ||
| 40 | try tokens.append(gpa, tok); | ||
| 41 | const column = tok.start - prev_line_last_col; | ||
| 42 | try line_col.append(gpa, .{ .line = line, .column = column }); | ||
| 43 | switch (tok.id) { | ||
| 44 | .invalid => { | ||
| 45 | return diags.failParse(path, "invalid token in LD script: '{s}' ({d}:{d})", .{ | ||
| 46 | std.fmt.fmtSliceEscapeLower(tok.get(data)), line, column, | ||
| 47 | }); | ||
| 48 | }, | ||
| 49 | .new_line => { | ||
| 50 | line += 1; | ||
| 51 | prev_line_last_col = tok.end; | ||
| 52 | }, | ||
| 53 | .eof => break, | ||
| 54 | else => {}, | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | var it: TokenIterator = .{ .tokens = tokens.items }; | ||
| 59 | var parser: Parser = .{ | ||
| 60 | .gpa = gpa, | ||
| 61 | .source = data, | ||
| 62 | .it = &it, | ||
| 63 | .args = .empty, | ||
| 64 | .cpu_arch = null, | ||
| 65 | }; | ||
| 66 | defer parser.args.deinit(gpa); | ||
| 67 | |||
| 68 | parser.start() catch |err| switch (err) { | ||
| 69 | error.UnexpectedToken => { | ||
| 70 | const last_token_id = parser.it.pos - 1; | ||
| 71 | const last_token = parser.it.get(last_token_id); | ||
| 72 | const lcol = line_col.items[last_token_id]; | ||
| 73 | return diags.failParse(path, "unexpected token in LD script: {s}: '{s}' ({d}:{d})", .{ | ||
| 74 | @tagName(last_token.id), | ||
| 75 | last_token.get(data), | ||
| 76 | lcol.line, | ||
| 77 | lcol.column, | ||
| 78 | }); | ||
| 79 | }, | ||
| 80 | else => |e| return e, | ||
| 81 | }; | ||
| 82 | return .{ | ||
| 83 | .path = path, | ||
| 84 | .cpu_arch = parser.cpu_arch, | ||
| 85 | .args = try parser.args.toOwnedSlice(gpa), | ||
| 86 | }; | ||
| 87 | } | ||
| 88 | |||
| 89 | const LineColumn = struct { | ||
| 90 | line: usize, | ||
| 91 | column: usize, | ||
| 92 | }; | ||
| 93 | |||
| 94 | const Command = enum { | ||
| 95 | output_format, | ||
| 96 | input, | ||
| 97 | group, | ||
| 98 | as_needed, | ||
| 99 | |||
| 100 | fn fromString(s: []const u8) ?Command { | ||
| 101 | inline for (@typeInfo(Command).@"enum".fields) |field| { | ||
| 102 | const upper_name = n: { | ||
| 103 | comptime var buf: [field.name.len]u8 = undefined; | ||
| 104 | inline for (field.name, 0..) |c, i| { | ||
| 105 | buf[i] = comptime std.ascii.toUpper(c); | ||
| 106 | } | ||
| 107 | break :n buf; | ||
| 108 | }; | ||
| 109 | if (std.mem.eql(u8, &upper_name, s)) return @field(Command, field.name); | ||
| 110 | } | ||
| 111 | return null; | ||
| 112 | } | ||
| 113 | }; | ||
| 114 | |||
| 115 | const Parser = struct { | ||
| 116 | gpa: Allocator, | ||
| 117 | source: []const u8, | ||
| 118 | it: *TokenIterator, | ||
| 119 | |||
| 120 | cpu_arch: ?std.Target.Cpu.Arch, | ||
| 121 | args: std.ArrayListUnmanaged(Arg), | ||
| 122 | |||
| 123 | fn start(parser: *Parser) !void { | ||
| 124 | while (true) { | ||
| 125 | parser.skipAny(&.{ .comment, .new_line }); | ||
| 126 | |||
| 127 | if (parser.maybe(.command)) |cmd_id| { | ||
| 128 | const cmd = parser.getCommand(cmd_id); | ||
| 129 | switch (cmd) { | ||
| 130 | .output_format => parser.cpu_arch = try parser.outputFormat(), | ||
| 131 | // TODO we should verify that group only contains libraries | ||
| 132 | .input, .group => try parser.group(), | ||
| 133 | else => return error.UnexpectedToken, | ||
| 134 | } | ||
| 135 | } else break; | ||
| 136 | } | ||
| 137 | |||
| 138 | if (parser.it.next()) |tok| switch (tok.id) { | ||
| 139 | .eof => {}, | ||
| 140 | else => return error.UnexpectedToken, | ||
| 141 | }; | ||
| 142 | } | ||
| 143 | |||
| 144 | fn outputFormat(p: *Parser) !std.Target.Cpu.Arch { | ||
| 145 | const value = value: { | ||
| 146 | if (p.skip(&.{.lparen})) { | ||
| 147 | const value_id = try p.require(.literal); | ||
| 148 | const value = p.it.get(value_id); | ||
| 149 | _ = try p.require(.rparen); | ||
| 150 | break :value value.get(p.source); | ||
| 151 | } else if (p.skip(&.{ .new_line, .lbrace })) { | ||
| 152 | const value_id = try p.require(.literal); | ||
| 153 | const value = p.it.get(value_id); | ||
| 154 | _ = p.skip(&.{.new_line}); | ||
| 155 | _ = try p.require(.rbrace); | ||
| 156 | break :value value.get(p.source); | ||
| 157 | } else return error.UnexpectedToken; | ||
| 158 | }; | ||
| 159 | if (std.mem.eql(u8, value, "elf64-x86-64")) return .x86_64; | ||
| 160 | if (std.mem.eql(u8, value, "elf64-littleaarch64")) return .aarch64; | ||
| 161 | return error.UnknownCpuArch; | ||
| 162 | } | ||
| 163 | |||
| 164 | fn group(p: *Parser) !void { | ||
| 165 | const gpa = p.gpa; | ||
| 166 | if (!p.skip(&.{.lparen})) return error.UnexpectedToken; | ||
| 167 | |||
| 168 | while (true) { | ||
| 169 | if (p.maybe(.literal)) |tok_id| { | ||
| 170 | const tok = p.it.get(tok_id); | ||
| 171 | const path = tok.get(p.source); | ||
| 172 | try p.args.append(gpa, .{ .path = path, .needed = true }); | ||
| 173 | } else if (p.maybe(.command)) |cmd_id| { | ||
| 174 | const cmd = p.getCommand(cmd_id); | ||
| 175 | switch (cmd) { | ||
| 176 | .as_needed => try p.asNeeded(), | ||
| 177 | else => return error.UnexpectedToken, | ||
| 178 | } | ||
| 179 | } else break; | ||
| 180 | } | ||
| 181 | |||
| 182 | _ = try p.require(.rparen); | ||
| 183 | } | ||
| 184 | |||
| 185 | fn asNeeded(p: *Parser) !void { | ||
| 186 | const gpa = p.gpa; | ||
| 187 | if (!p.skip(&.{.lparen})) return error.UnexpectedToken; | ||
| 188 | |||
| 189 | while (p.maybe(.literal)) |tok_id| { | ||
| 190 | const tok = p.it.get(tok_id); | ||
| 191 | const path = tok.get(p.source); | ||
| 192 | try p.args.append(gpa, .{ .path = path, .needed = false }); | ||
| 193 | } | ||
| 194 | |||
| 195 | _ = try p.require(.rparen); | ||
| 196 | } | ||
| 197 | |||
| 198 | fn skip(p: *Parser, comptime ids: []const Token.Id) bool { | ||
| 199 | const pos = p.it.pos; | ||
| 200 | inline for (ids) |id| { | ||
| 201 | const tok = p.it.next() orelse return false; | ||
| 202 | if (tok.id != id) { | ||
| 203 | p.it.seekTo(pos); | ||
| 204 | return false; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | return true; | ||
| 208 | } | ||
| 209 | |||
| 210 | fn skipAny(p: *Parser, comptime ids: []const Token.Id) void { | ||
| 211 | outer: while (p.it.next()) |tok| { | ||
| 212 | inline for (ids) |id| { | ||
| 213 | if (id == tok.id) continue :outer; | ||
| 214 | } | ||
| 215 | break p.it.seekBy(-1); | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | fn maybe(p: *Parser, comptime id: Token.Id) ?Token.Index { | ||
| 220 | const pos = p.it.pos; | ||
| 221 | const tok = p.it.next() orelse return null; | ||
| 222 | if (tok.id == id) return pos; | ||
| 223 | p.it.seekBy(-1); | ||
| 224 | return null; | ||
| 225 | } | ||
| 226 | |||
| 227 | fn require(p: *Parser, comptime id: Token.Id) !Token.Index { | ||
| 228 | return p.maybe(id) orelse return error.UnexpectedToken; | ||
| 229 | } | ||
| 230 | |||
| 231 | fn getCommand(p: *Parser, index: Token.Index) Command { | ||
| 232 | const tok = p.it.get(index); | ||
| 233 | assert(tok.id == .command); | ||
| 234 | return Command.fromString(tok.get(p.source)).?; | ||
| 235 | } | ||
| 236 | }; | ||
| 237 | |||
| 238 | const Token = struct { | ||
| 239 | id: Id, | ||
| 240 | start: usize, | ||
| 241 | end: usize, | ||
| 242 | |||
| 243 | const Id = enum { | ||
| 244 | eof, | ||
| 245 | invalid, | ||
| 246 | |||
| 247 | new_line, | ||
| 248 | lparen, // ( | ||
| 249 | rparen, // ) | ||
| 250 | lbrace, // { | ||
| 251 | rbrace, // } | ||
| 252 | |||
| 253 | comment, // /* */ | ||
| 254 | |||
| 255 | command, // literal with special meaning, see Command | ||
| 256 | literal, | ||
| 257 | }; | ||
| 258 | |||
| 259 | const Index = usize; | ||
| 260 | |||
| 261 | fn get(tok: Token, source: []const u8) []const u8 { | ||
| 262 | return source[tok.start..tok.end]; | ||
| 263 | } | ||
| 264 | }; | ||
| 265 | |||
| 266 | const Tokenizer = struct { | ||
| 267 | source: []const u8, | ||
| 268 | index: usize = 0, | ||
| 269 | |||
| 270 | fn matchesPattern(comptime pattern: []const u8, slice: []const u8) bool { | ||
| 271 | comptime var count: usize = 0; | ||
| 272 | inline while (count < pattern.len) : (count += 1) { | ||
| 273 | if (count >= slice.len) return false; | ||
| 274 | const c = slice[count]; | ||
| 275 | if (pattern[count] != c) return false; | ||
| 276 | } | ||
| 277 | return true; | ||
| 278 | } | ||
| 279 | |||
| 280 | fn matches(tok: Tokenizer, comptime pattern: []const u8) bool { | ||
| 281 | return matchesPattern(pattern, tok.source[tok.index..]); | ||
| 282 | } | ||
| 283 | |||
| 284 | fn isCommand(tok: Tokenizer, start: usize, end: usize) bool { | ||
| 285 | return if (Command.fromString(tok.source[start..end]) == null) false else true; | ||
| 286 | } | ||
| 287 | |||
| 288 | fn next(tok: *Tokenizer) Token { | ||
| 289 | var result = Token{ | ||
| 290 | .id = .eof, | ||
| 291 | .start = tok.index, | ||
| 292 | .end = undefined, | ||
| 293 | }; | ||
| 294 | |||
| 295 | var state: enum { | ||
| 296 | start, | ||
| 297 | comment, | ||
| 298 | literal, | ||
| 299 | } = .start; | ||
| 300 | |||
| 301 | while (tok.index < tok.source.len) : (tok.index += 1) { | ||
| 302 | const c = tok.source[tok.index]; | ||
| 303 | switch (state) { | ||
| 304 | .start => switch (c) { | ||
| 305 | ' ', '\t' => result.start += 1, | ||
| 306 | |||
| 307 | '\n' => { | ||
| 308 | result.id = .new_line; | ||
| 309 | tok.index += 1; | ||
| 310 | break; | ||
| 311 | }, | ||
| 312 | |||
| 313 | '\r' => { | ||
| 314 | if (tok.matches("\r\n")) { | ||
| 315 | result.id = .new_line; | ||
| 316 | tok.index += "\r\n".len; | ||
| 317 | } else { | ||
| 318 | result.id = .invalid; | ||
| 319 | tok.index += 1; | ||
| 320 | } | ||
| 321 | break; | ||
| 322 | }, | ||
| 323 | |||
| 324 | '/' => if (tok.matches("/*")) { | ||
| 325 | state = .comment; | ||
| 326 | tok.index += "/*".len; | ||
| 327 | } else { | ||
| 328 | state = .literal; | ||
| 329 | }, | ||
| 330 | |||
| 331 | '(' => { | ||
| 332 | result.id = .lparen; | ||
| 333 | tok.index += 1; | ||
| 334 | break; | ||
| 335 | }, | ||
| 336 | |||
| 337 | ')' => { | ||
| 338 | result.id = .rparen; | ||
| 339 | tok.index += 1; | ||
| 340 | break; | ||
| 341 | }, | ||
| 342 | |||
| 343 | '{' => { | ||
| 344 | result.id = .lbrace; | ||
| 345 | tok.index += 1; | ||
| 346 | break; | ||
| 347 | }, | ||
| 348 | |||
| 349 | '}' => { | ||
| 350 | result.id = .rbrace; | ||
| 351 | tok.index += 1; | ||
| 352 | break; | ||
| 353 | }, | ||
| 354 | |||
| 355 | else => state = .literal, | ||
| 356 | }, | ||
| 357 | |||
| 358 | .comment => switch (c) { | ||
| 359 | '*' => if (tok.matches("*/")) { | ||
| 360 | result.id = .comment; | ||
| 361 | tok.index += "*/".len; | ||
| 362 | break; | ||
| 363 | }, | ||
| 364 | else => {}, | ||
| 365 | }, | ||
| 366 | |||
| 367 | .literal => switch (c) { | ||
| 368 | ' ', '(', '\n' => { | ||
| 369 | if (tok.isCommand(result.start, tok.index)) { | ||
| 370 | result.id = .command; | ||
| 371 | } else { | ||
| 372 | result.id = .literal; | ||
| 373 | } | ||
| 374 | break; | ||
| 375 | }, | ||
| 376 | |||
| 377 | ')' => { | ||
| 378 | result.id = .literal; | ||
| 379 | break; | ||
| 380 | }, | ||
| 381 | |||
| 382 | '\r' => { | ||
| 383 | if (tok.matches("\r\n")) { | ||
| 384 | if (tok.isCommand(result.start, tok.index)) { | ||
| 385 | result.id = .command; | ||
| 386 | } else { | ||
| 387 | result.id = .literal; | ||
| 388 | } | ||
| 389 | } else { | ||
| 390 | result.id = .invalid; | ||
| 391 | tok.index += 1; | ||
| 392 | } | ||
| 393 | break; | ||
| 394 | }, | ||
| 395 | |||
| 396 | else => {}, | ||
| 397 | }, | ||
| 398 | } | ||
| 399 | } | ||
| 400 | |||
| 401 | result.end = tok.index; | ||
| 402 | return result; | ||
| 403 | } | ||
| 404 | }; | ||
| 405 | |||
| 406 | const TokenIterator = struct { | ||
| 407 | tokens: []const Token, | ||
| 408 | pos: Token.Index = 0, | ||
| 409 | |||
| 410 | fn next(it: *TokenIterator) ?Token { | ||
| 411 | const token = it.peek() orelse return null; | ||
| 412 | it.pos += 1; | ||
| 413 | return token; | ||
| 414 | } | ||
| 415 | |||
| 416 | fn peek(it: TokenIterator) ?Token { | ||
| 417 | if (it.pos >= it.tokens.len) return null; | ||
| 418 | return it.tokens[it.pos]; | ||
| 419 | } | ||
| 420 | |||
| 421 | fn reset(it: *TokenIterator) void { | ||
| 422 | it.pos = 0; | ||
| 423 | } | ||
| 424 | |||
| 425 | fn seekTo(it: *TokenIterator, pos: Token.Index) void { | ||
| 426 | it.pos = pos; | ||
| 427 | } | ||
| 428 | |||
| 429 | fn seekBy(it: *TokenIterator, offset: isize) void { | ||
| 430 | const new_pos = @as(isize, @bitCast(it.pos)) + offset; | ||
| 431 | if (new_pos < 0) { | ||
| 432 | it.pos = 0; | ||
| 433 | } else { | ||
| 434 | it.pos = @as(usize, @intCast(new_pos)); | ||
| 435 | } | ||
| 436 | } | ||
| 437 | |||
| 438 | fn get(it: *TokenIterator, pos: Token.Index) Token { | ||
| 439 | assert(pos < it.tokens.len); | ||
| 440 | return it.tokens[pos]; | ||
| 441 | } | ||
| 442 | }; | ||
| 443 | |||
| 444 | const LdScript = @This(); | ||
| 445 | const Diags = @import("../../link.zig").Diags; | ||
| 446 | |||
| 447 | const std = @import("std"); | ||
| 448 | const assert = std.debug.assert; | ||
| 449 | const Path = std.Build.Cache.Path; | ||
| 450 | const Allocator = std.mem.Allocator; | ||
src/link/LdScript.zig created+450| ... | @@ -0,0 +1,450 @@ | ||
| 1 | path: Path, | ||
| 2 | cpu_arch: ?std.Target.Cpu.Arch, | ||
| 3 | args: []const Arg, | ||
| 4 | |||
| 5 | pub const Arg = struct { | ||
| 6 | needed: bool = false, | ||
| 7 | path: []const u8, | ||
| 8 | }; | ||
| 9 | |||
| 10 | pub fn deinit(ls: *LdScript, gpa: Allocator) void { | ||
| 11 | gpa.free(ls.args); | ||
| 12 | ls.* = undefined; | ||
| 13 | } | ||
| 14 | |||
| 15 | pub const Error = error{ | ||
| 16 | LinkFailure, | ||
| 17 | UnexpectedToken, | ||
| 18 | UnknownCpuArch, | ||
| 19 | OutOfMemory, | ||
| 20 | }; | ||
| 21 | |||
| 22 | pub fn parse( | ||
| 23 | gpa: Allocator, | ||
| 24 | diags: *Diags, | ||
| 25 | /// For error reporting. | ||
| 26 | path: Path, | ||
| 27 | data: []const u8, | ||
| 28 | ) Error!LdScript { | ||
| 29 | var tokenizer = Tokenizer{ .source = data }; | ||
| 30 | var tokens: std.ArrayListUnmanaged(Token) = .empty; | ||
| 31 | defer tokens.deinit(gpa); | ||
| 32 | var line_col: std.ArrayListUnmanaged(LineColumn) = .empty; | ||
| 33 | defer line_col.deinit(gpa); | ||
| 34 | |||
| 35 | var line: usize = 0; | ||
| 36 | var prev_line_last_col: usize = 0; | ||
| 37 | |||
| 38 | while (true) { | ||
| 39 | const tok = tokenizer.next(); | ||
| 40 | try tokens.append(gpa, tok); | ||
| 41 | const column = tok.start - prev_line_last_col; | ||
| 42 | try line_col.append(gpa, .{ .line = line, .column = column }); | ||
| 43 | switch (tok.id) { | ||
| 44 | .invalid => { | ||
| 45 | return diags.failParse(path, "invalid token in LD script: '{s}' ({d}:{d})", .{ | ||
| 46 | std.fmt.fmtSliceEscapeLower(tok.get(data)), line, column, | ||
| 47 | }); | ||
| 48 | }, | ||
| 49 | .new_line => { | ||
| 50 | line += 1; | ||
| 51 | prev_line_last_col = tok.end; | ||
| 52 | }, | ||
| 53 | .eof => break, | ||
| 54 | else => {}, | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | var it: TokenIterator = .{ .tokens = tokens.items }; | ||
| 59 | var parser: Parser = .{ | ||
| 60 | .gpa = gpa, | ||
| 61 | .source = data, | ||
| 62 | .it = &it, | ||
| 63 | .args = .empty, | ||
| 64 | .cpu_arch = null, | ||
| 65 | }; | ||
| 66 | defer parser.args.deinit(gpa); | ||
| 67 | |||
| 68 | parser.start() catch |err| switch (err) { | ||
| 69 | error.UnexpectedToken => { | ||
| 70 | const last_token_id = parser.it.pos - 1; | ||
| 71 | const last_token = parser.it.get(last_token_id); | ||
| 72 | const lcol = line_col.items[last_token_id]; | ||
| 73 | return diags.failParse(path, "unexpected token in LD script: {s}: '{s}' ({d}:{d})", .{ | ||
| 74 | @tagName(last_token.id), | ||
| 75 | last_token.get(data), | ||
| 76 | lcol.line, | ||
| 77 | lcol.column, | ||
| 78 | }); | ||
| 79 | }, | ||
| 80 | else => |e| return e, | ||
| 81 | }; | ||
| 82 | return .{ | ||
| 83 | .path = path, | ||
| 84 | .cpu_arch = parser.cpu_arch, | ||
| 85 | .args = try parser.args.toOwnedSlice(gpa), | ||
| 86 | }; | ||
| 87 | } | ||
| 88 | |||
| 89 | const LineColumn = struct { | ||
| 90 | line: usize, | ||
| 91 | column: usize, | ||
| 92 | }; | ||
| 93 | |||
| 94 | const Command = enum { | ||
| 95 | output_format, | ||
| 96 | input, | ||
| 97 | group, | ||
| 98 | as_needed, | ||
| 99 | |||
| 100 | fn fromString(s: []const u8) ?Command { | ||
| 101 | inline for (@typeInfo(Command).@"enum".fields) |field| { | ||
| 102 | const upper_name = n: { | ||
| 103 | comptime var buf: [field.name.len]u8 = undefined; | ||
| 104 | inline for (field.name, 0..) |c, i| { | ||
| 105 | buf[i] = comptime std.ascii.toUpper(c); | ||
| 106 | } | ||
| 107 | break :n buf; | ||
| 108 | }; | ||
| 109 | if (std.mem.eql(u8, &upper_name, s)) return @field(Command, field.name); | ||
| 110 | } | ||
| 111 | return null; | ||
| 112 | } | ||
| 113 | }; | ||
| 114 | |||
| 115 | const Parser = struct { | ||
| 116 | gpa: Allocator, | ||
| 117 | source: []const u8, | ||
| 118 | it: *TokenIterator, | ||
| 119 | |||
| 120 | cpu_arch: ?std.Target.Cpu.Arch, | ||
| 121 | args: std.ArrayListUnmanaged(Arg), | ||
| 122 | |||
| 123 | fn start(parser: *Parser) !void { | ||
| 124 | while (true) { | ||
| 125 | parser.skipAny(&.{ .comment, .new_line }); | ||
| 126 | |||
| 127 | if (parser.maybe(.command)) |cmd_id| { | ||
| 128 | const cmd = parser.getCommand(cmd_id); | ||
| 129 | switch (cmd) { | ||
| 130 | .output_format => parser.cpu_arch = try parser.outputFormat(), | ||
| 131 | // TODO we should verify that group only contains libraries | ||
| 132 | .input, .group => try parser.group(), | ||
| 133 | else => return error.UnexpectedToken, | ||
| 134 | } | ||
| 135 | } else break; | ||
| 136 | } | ||
| 137 | |||
| 138 | if (parser.it.next()) |tok| switch (tok.id) { | ||
| 139 | .eof => {}, | ||
| 140 | else => return error.UnexpectedToken, | ||
| 141 | }; | ||
| 142 | } | ||
| 143 | |||
| 144 | fn outputFormat(p: *Parser) !std.Target.Cpu.Arch { | ||
| 145 | const value = value: { | ||
| 146 | if (p.skip(&.{.lparen})) { | ||
| 147 | const value_id = try p.require(.literal); | ||
| 148 | const value = p.it.get(value_id); | ||
| 149 | _ = try p.require(.rparen); | ||
| 150 | break :value value.get(p.source); | ||
| 151 | } else if (p.skip(&.{ .new_line, .lbrace })) { | ||
| 152 | const value_id = try p.require(.literal); | ||
| 153 | const value = p.it.get(value_id); | ||
| 154 | _ = p.skip(&.{.new_line}); | ||
| 155 | _ = try p.require(.rbrace); | ||
| 156 | break :value value.get(p.source); | ||
| 157 | } else return error.UnexpectedToken; | ||
| 158 | }; | ||
| 159 | if (std.mem.eql(u8, value, "elf64-x86-64")) return .x86_64; | ||
| 160 | if (std.mem.eql(u8, value, "elf64-littleaarch64")) return .aarch64; | ||
| 161 | return error.UnknownCpuArch; | ||
| 162 | } | ||
| 163 | |||
| 164 | fn group(p: *Parser) !void { | ||
| 165 | const gpa = p.gpa; | ||
| 166 | if (!p.skip(&.{.lparen})) return error.UnexpectedToken; | ||
| 167 | |||
| 168 | while (true) { | ||
| 169 | if (p.maybe(.literal)) |tok_id| { | ||
| 170 | const tok = p.it.get(tok_id); | ||
| 171 | const path = tok.get(p.source); | ||
| 172 | try p.args.append(gpa, .{ .path = path, .needed = true }); | ||
| 173 | } else if (p.maybe(.command)) |cmd_id| { | ||
| 174 | const cmd = p.getCommand(cmd_id); | ||
| 175 | switch (cmd) { | ||
| 176 | .as_needed => try p.asNeeded(), | ||
| 177 | else => return error.UnexpectedToken, | ||
| 178 | } | ||
| 179 | } else break; | ||
| 180 | } | ||
| 181 | |||
| 182 | _ = try p.require(.rparen); | ||
| 183 | } | ||
| 184 | |||
| 185 | fn asNeeded(p: *Parser) !void { | ||
| 186 | const gpa = p.gpa; | ||
| 187 | if (!p.skip(&.{.lparen})) return error.UnexpectedToken; | ||
| 188 | |||
| 189 | while (p.maybe(.literal)) |tok_id| { | ||
| 190 | const tok = p.it.get(tok_id); | ||
| 191 | const path = tok.get(p.source); | ||
| 192 | try p.args.append(gpa, .{ .path = path, .needed = false }); | ||
| 193 | } | ||
| 194 | |||
| 195 | _ = try p.require(.rparen); | ||
| 196 | } | ||
| 197 | |||
| 198 | fn skip(p: *Parser, comptime ids: []const Token.Id) bool { | ||
| 199 | const pos = p.it.pos; | ||
| 200 | inline for (ids) |id| { | ||
| 201 | const tok = p.it.next() orelse return false; | ||
| 202 | if (tok.id != id) { | ||
| 203 | p.it.seekTo(pos); | ||
| 204 | return false; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | return true; | ||
| 208 | } | ||
| 209 | |||
| 210 | fn skipAny(p: *Parser, comptime ids: []const Token.Id) void { | ||
| 211 | outer: while (p.it.next()) |tok| { | ||
| 212 | inline for (ids) |id| { | ||
| 213 | if (id == tok.id) continue :outer; | ||
| 214 | } | ||
| 215 | break p.it.seekBy(-1); | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | fn maybe(p: *Parser, comptime id: Token.Id) ?Token.Index { | ||
| 220 | const pos = p.it.pos; | ||
| 221 | const tok = p.it.next() orelse return null; | ||
| 222 | if (tok.id == id) return pos; | ||
| 223 | p.it.seekBy(-1); | ||
| 224 | return null; | ||
| 225 | } | ||
| 226 | |||
| 227 | fn require(p: *Parser, comptime id: Token.Id) !Token.Index { | ||
| 228 | return p.maybe(id) orelse return error.UnexpectedToken; | ||
| 229 | } | ||
| 230 | |||
| 231 | fn getCommand(p: *Parser, index: Token.Index) Command { | ||
| 232 | const tok = p.it.get(index); | ||
| 233 | assert(tok.id == .command); | ||
| 234 | return Command.fromString(tok.get(p.source)).?; | ||
| 235 | } | ||
| 236 | }; | ||
| 237 | |||
| 238 | const Token = struct { | ||
| 239 | id: Id, | ||
| 240 | start: usize, | ||
| 241 | end: usize, | ||
| 242 | |||
| 243 | const Id = enum { | ||
| 244 | eof, | ||
| 245 | invalid, | ||
| 246 | |||
| 247 | new_line, | ||
| 248 | lparen, // ( | ||
| 249 | rparen, // ) | ||
| 250 | lbrace, // { | ||
| 251 | rbrace, // } | ||
| 252 | |||
| 253 | comment, // /* */ | ||
| 254 | |||
| 255 | command, // literal with special meaning, see Command | ||
| 256 | literal, | ||
| 257 | }; | ||
| 258 | |||
| 259 | const Index = usize; | ||
| 260 | |||
| 261 | fn get(tok: Token, source: []const u8) []const u8 { | ||
| 262 | return source[tok.start..tok.end]; | ||
| 263 | } | ||
| 264 | }; | ||
| 265 | |||
| 266 | const Tokenizer = struct { | ||
| 267 | source: []const u8, | ||
| 268 | index: usize = 0, | ||
| 269 | |||
| 270 | fn matchesPattern(comptime pattern: []const u8, slice: []const u8) bool { | ||
| 271 | comptime var count: usize = 0; | ||
| 272 | inline while (count < pattern.len) : (count += 1) { | ||
| 273 | if (count >= slice.len) return false; | ||
| 274 | const c = slice[count]; | ||
| 275 | if (pattern[count] != c) return false; | ||
| 276 | } | ||
| 277 | return true; | ||
| 278 | } | ||
| 279 | |||
| 280 | fn matches(tok: Tokenizer, comptime pattern: []const u8) bool { | ||
| 281 | return matchesPattern(pattern, tok.source[tok.index..]); | ||
| 282 | } | ||
| 283 | |||
| 284 | fn isCommand(tok: Tokenizer, start: usize, end: usize) bool { | ||
| 285 | return if (Command.fromString(tok.source[start..end]) == null) false else true; | ||
| 286 | } | ||
| 287 | |||
| 288 | fn next(tok: *Tokenizer) Token { | ||
| 289 | var result = Token{ | ||
| 290 | .id = .eof, | ||
| 291 | .start = tok.index, | ||
| 292 | .end = undefined, | ||
| 293 | }; | ||
| 294 | |||
| 295 | var state: enum { | ||
| 296 | start, | ||
| 297 | comment, | ||
| 298 | literal, | ||
| 299 | } = .start; | ||
| 300 | |||
| 301 | while (tok.index < tok.source.len) : (tok.index += 1) { | ||
| 302 | const c = tok.source[tok.index]; | ||
| 303 | switch (state) { | ||
| 304 | .start => switch (c) { | ||
| 305 | ' ', '\t' => result.start += 1, | ||
| 306 | |||
| 307 | '\n' => { | ||
| 308 | result.id = .new_line; | ||
| 309 | tok.index += 1; | ||
| 310 | break; | ||
| 311 | }, | ||
| 312 | |||
| 313 | '\r' => { | ||
| 314 | if (tok.matches("\r\n")) { | ||
| 315 | result.id = .new_line; | ||
| 316 | tok.index += "\r\n".len; | ||
| 317 | } else { | ||
| 318 | result.id = .invalid; | ||
| 319 | tok.index += 1; | ||
| 320 | } | ||
| 321 | break; | ||
| 322 | }, | ||
| 323 | |||
| 324 | '/' => if (tok.matches("/*")) { | ||
| 325 | state = .comment; | ||
| 326 | tok.index += "/*".len; | ||
| 327 | } else { | ||
| 328 | state = .literal; | ||
| 329 | }, | ||
| 330 | |||
| 331 | '(' => { | ||
| 332 | result.id = .lparen; | ||
| 333 | tok.index += 1; | ||
| 334 | break; | ||
| 335 | }, | ||
| 336 | |||
| 337 | ')' => { | ||
| 338 | result.id = .rparen; | ||
| 339 | tok.index += 1; | ||
| 340 | break; | ||
| 341 | }, | ||
| 342 | |||
| 343 | '{' => { | ||
| 344 | result.id = .lbrace; | ||
| 345 | tok.index += 1; | ||
| 346 | break; | ||
| 347 | }, | ||
| 348 | |||
| 349 | '}' => { | ||
| 350 | result.id = .rbrace; | ||
| 351 | tok.index += 1; | ||
| 352 | break; | ||
| 353 | }, | ||
| 354 | |||
| 355 | else => state = .literal, | ||
| 356 | }, | ||
| 357 | |||
| 358 | .comment => switch (c) { | ||
| 359 | '*' => if (tok.matches("*/")) { | ||
| 360 | result.id = .comment; | ||
| 361 | tok.index += "*/".len; | ||
| 362 | break; | ||
| 363 | }, | ||
| 364 | else => {}, | ||
| 365 | }, | ||
| 366 | |||
| 367 | .literal => switch (c) { | ||
| 368 | ' ', '(', '\n' => { | ||
| 369 | if (tok.isCommand(result.start, tok.index)) { | ||
| 370 | result.id = .command; | ||
| 371 | } else { | ||
| 372 | result.id = .literal; | ||
| 373 | } | ||
| 374 | break; | ||
| 375 | }, | ||
| 376 | |||
| 377 | ')' => { | ||
| 378 | result.id = .literal; | ||
| 379 | break; | ||
| 380 | }, | ||
| 381 | |||
| 382 | '\r' => { | ||
| 383 | if (tok.matches("\r\n")) { | ||
| 384 | if (tok.isCommand(result.start, tok.index)) { | ||
| 385 | result.id = .command; | ||
| 386 | } else { | ||
| 387 | result.id = .literal; | ||
| 388 | } | ||
| 389 | } else { | ||
| 390 | result.id = .invalid; | ||
| 391 | tok.index += 1; | ||
| 392 | } | ||
| 393 | break; | ||
| 394 | }, | ||
| 395 | |||
| 396 | else => {}, | ||
| 397 | }, | ||
| 398 | } | ||
| 399 | } | ||
| 400 | |||
| 401 | result.end = tok.index; | ||
| 402 | return result; | ||
| 403 | } | ||
| 404 | }; | ||
| 405 | |||
| 406 | const TokenIterator = struct { | ||
| 407 | tokens: []const Token, | ||
| 408 | pos: Token.Index = 0, | ||
| 409 | |||
| 410 | fn next(it: *TokenIterator) ?Token { | ||
| 411 | const token = it.peek() orelse return null; | ||
| 412 | it.pos += 1; | ||
| 413 | return token; | ||
| 414 | } | ||
| 415 | |||
| 416 | fn peek(it: TokenIterator) ?Token { | ||
| 417 | if (it.pos >= it.tokens.len) return null; | ||
| 418 | return it.tokens[it.pos]; | ||
| 419 | } | ||
| 420 | |||
| 421 | fn reset(it: *TokenIterator) void { | ||
| 422 | it.pos = 0; | ||
| 423 | } | ||
| 424 | |||
| 425 | fn seekTo(it: *TokenIterator, pos: Token.Index) void { | ||
| 426 | it.pos = pos; | ||
| 427 | } | ||
| 428 | |||
| 429 | fn seekBy(it: *TokenIterator, offset: isize) void { | ||
| 430 | const new_pos = @as(isize, @bitCast(it.pos)) + offset; | ||
| 431 | if (new_pos < 0) { | ||
| 432 | it.pos = 0; | ||
| 433 | } else { | ||
| 434 | it.pos = @as(usize, @intCast(new_pos)); | ||
| 435 | } | ||
| 436 | } | ||
| 437 | |||
| 438 | fn get(it: *TokenIterator, pos: Token.Index) Token { | ||
| 439 | assert(pos < it.tokens.len); | ||
| 440 | return it.tokens[pos]; | ||
| 441 | } | ||
| 442 | }; | ||
| 443 | |||
| 444 | const LdScript = @This(); | ||
| 445 | const Diags = @import("../link.zig").Diags; | ||
| 446 | |||
| 447 | const std = @import("std"); | ||
| 448 | const assert = std.debug.assert; | ||
| 449 | const Path = std.Build.Cache.Path; | ||
| 450 | const Allocator = std.mem.Allocator; | ||