| 1 | //! High-level Intermediate Representation is an instruction list format that |
| 2 | //! describes a source file in a manner effective for Semantic Analysis to |
| 3 | //! process. The root instruction of every Hir is a `.module` that contains the |
| 4 | //! root declarations. Each declaration's value contains a list of instructions. |
| 5 | //! In contrast to the nested nature of syntax trees, Hir represents nested |
| 6 | //! expressions as flat instruction sequences. |
| 7 | //! |
| 8 | //! Hir for a given file has no dependencies on it's imports, meaning it can be |
| 9 | //! computed in parallel and then cached when not changed. |
| 10 | //! |
| 11 | //! A helper program exists to visualize Hir: './build transform -- ./file.ix' |
| 12 | //! Alternatively, `ix build` has the `--dump-ir` option. |
| 13 | |
| 14 | insts: Inst.List.Slice, |
| 15 | string_bytes: []const u8, |
| 16 | extra: ExtraStore, |
| 17 | report: Report, |
| 18 | |
| 19 | pub const ExtraStore = dod.ExtraStore(.hir, .@"const"); |
| 20 | pub const EncodedData = dod.EncodedData(Tag, u64); |
| 21 | pub const ExtraIndex = ExtraStore.Index; |
| 22 | pub const Dump = @import("Hir.Dump.zig"); |
| 23 | |
| 24 | pub const Tag = enum(u8) { |
| 25 | /// Either the top level module or a `module` keyword. Contains a list of `.declarations` |
| 26 | /// Every analyzed module gets a `Comp.Namespace` |
| 27 | module, |
| 28 | /// Smallest unit of re-compilation. Every analyzed declaration gets a `Comp.Decl` |
| 29 | declaration, |
| 30 | /// Top-level memory location |
| 31 | variable_declaration, |
| 32 | /// Test declaration |
| 33 | test_declaration, |
| 34 | /// Retrieves a declaration value via it's name. Since declarations are |
| 35 | /// analyzed in isolation of each other, all references to instructions |
| 36 | /// outside of the current declaration must be in the form of this. |
| 37 | load_decl, |
| 38 | /// Blocks are a list of instructions. The block will end with a `.break`, |
| 39 | /// which will assign the block a return value. |
| 40 | block, |
| 41 | /// Ends a block (current or any parent) with a value. |
| 42 | /// `return` expressions are lowered to this. |
| 43 | @"break", |
| 44 | /// Creates a memory cell in the current scope with a starting value. |
| 45 | memory, |
| 46 | /// Re-assign a memory cell with a new ref. |
| 47 | store, |
| 48 | /// Re-assign a global variable decl with a new ref. |
| 49 | store_decl, |
| 50 | /// Re-assign a field with a new ref |
| 51 | store_field, |
| 52 | /// Performs a type coercion, such as in variable declarations. |
| 53 | /// The resulting Air Ref will be guaranteed to be the coerced type. |
| 54 | coercion, |
| 55 | /// Load a parameter |
| 56 | /// if data value is maxInt(u32), the function return type is used |
| 57 | param, |
| 58 | /// Asserts the operand is not void. Used for discard statements. |
| 59 | discard, |
| 60 | /// Asserts the operand is `void`. Used for undiscarded expressions. |
| 61 | ensure_void, |
| 62 | |
| 63 | // TODO: Rename to `.branch` |
| 64 | /// Given a boolean condition, pick one of two branches. This is used to |
| 65 | /// implement `if` statements as well as boolean `and` / `or` operators. |
| 66 | @"if", |
| 67 | /// Given a block instruction, evaluate to a Value without generating |
| 68 | /// any runtime instructions. |
| 69 | @"comptime", |
| 70 | |
| 71 | /// A function expression, with or without captures |
| 72 | func, |
| 73 | |
| 74 | // The following instructions are used to implement `Transform.ResultType` |
| 75 | result_type_argument, |
| 76 | result_type_struct_field, |
| 77 | result_type_tuple_field, |
| 78 | result_type_array_value, |
| 79 | |
| 80 | /// Numeric constant. |
| 81 | number, |
| 82 | /// String literal constant. |
| 83 | string, |
| 84 | |
| 85 | /// Constructs a unique struct type. |
| 86 | struct_decl, |
| 87 | /// Constructs an array type |
| 88 | array_type, |
| 89 | |
| 90 | struct_init, |
| 91 | array_init, |
| 92 | |
| 93 | tuple_init, |
| 94 | tuple_type, |
| 95 | |
| 96 | add, |
| 97 | sub, |
| 98 | mul, |
| 99 | div, |
| 100 | rem, |
| 101 | pow, |
| 102 | @"and", |
| 103 | @"or", |
| 104 | bit_and, |
| 105 | bit_or, |
| 106 | bit_xor, |
| 107 | shl, |
| 108 | shr, |
| 109 | eq, |
| 110 | ne, |
| 111 | lt, |
| 112 | le, |
| 113 | gt, |
| 114 | ge, |
| 115 | |
| 116 | negate, |
| 117 | bool_not, |
| 118 | bit_not, |
| 119 | assert, |
| 120 | field, |
| 121 | index, |
| 122 | call, |
| 123 | type_of, |
| 124 | /// Implements the `@extern` builtin |
| 125 | @"extern", |
| 126 | |
| 127 | /// Implements the `@jsNew` builtin |
| 128 | js_new, |
| 129 | /// Implements the `@import` builtin |
| 130 | import, |
| 131 | /// Implements the `@fetch` builtin |
| 132 | fetch, |
| 133 | /// Implements the `@compileError` builtin |
| 134 | compile_error, |
| 135 | |
| 136 | pub fn Data(comptime tag: Tag) type { |
| 137 | return switch (tag) { |
| 138 | .module, .block, .@"comptime" => PlNode(IndexList), |
| 139 | |
| 140 | .declaration, |
| 141 | .variable_declaration, |
| 142 | .test_declaration, |
| 143 | => PlNode(Decl), |
| 144 | |
| 145 | .coercion => PlNode(Coercion), |
| 146 | .@"break" => PlNode(Break), |
| 147 | .number => PlNode(Float), |
| 148 | .string => PlNode(SlicedString), |
| 149 | .call => PlNode(Call), |
| 150 | .@"if" => PlNode(If), |
| 151 | .store => PlNode(Store), |
| 152 | .store_decl => PlNode(StoreVarDecl), |
| 153 | .store_field => PlNode(StoreField), |
| 154 | .js_new => PlNode(JsNew), |
| 155 | .struct_decl => PlNode(StructDecl), |
| 156 | .struct_init => PlNode(StructInit), |
| 157 | .array_init => PlNode(ArrayInit), |
| 158 | .tuple_type => PlNode(TupleType), |
| 159 | .tuple_init => PlNode(TupleInit), |
| 160 | .func => PlNode(Func), |
| 161 | |
| 162 | .load_decl => StrNode, |
| 163 | .param => DataNode, |
| 164 | |
| 165 | .result_type_argument => ResultTypeArgument, |
| 166 | .result_type_struct_field => ResultTypeStructField, |
| 167 | .result_type_tuple_field => ResultTypeTupleField, |
| 168 | .result_type_array_value => ResultTypeArrayValue, |
| 169 | |
| 170 | // One operand instructions |
| 171 | .memory, |
| 172 | .discard, |
| 173 | .ensure_void, |
| 174 | .negate, |
| 175 | .bool_not, |
| 176 | .bit_not, |
| 177 | .@"extern", |
| 178 | .import, |
| 179 | .fetch, |
| 180 | .compile_error, |
| 181 | .type_of, |
| 182 | .array_type, |
| 183 | .assert, |
| 184 | => RefNode, |
| 185 | |
| 186 | // Two operand instructions |
| 187 | .field, |
| 188 | .index, |
| 189 | .add, |
| 190 | .sub, |
| 191 | .mul, |
| 192 | .div, |
| 193 | .rem, |
| 194 | .pow, |
| 195 | .@"and", |
| 196 | .@"or", |
| 197 | .bit_and, |
| 198 | .bit_or, |
| 199 | .bit_xor, |
| 200 | .shl, |
| 201 | .shr, |
| 202 | .eq, |
| 203 | .ne, |
| 204 | .lt, |
| 205 | .le, |
| 206 | .gt, |
| 207 | .ge, |
| 208 | => PlNode(Bin), |
| 209 | }; |
| 210 | } |
| 211 | |
| 212 | pub fn airTag(comptime tag: Tag) Air.Inst.Tag { |
| 213 | return @field(Air.Inst.Tag, @tagName(tag)); |
| 214 | } |
| 215 | }; |
| 216 | |
| 217 | pub fn PlNode(comptime Payload: type) type { |
| 218 | return struct { |
| 219 | pub const T = Payload; |
| 220 | |
| 221 | src_node: NodeOffset, |
| 222 | extra_index: ExtraIndex(Payload), |
| 223 | }; |
| 224 | } |
| 225 | |
| 226 | pub const RefNode = struct { |
| 227 | src_node: NodeOffset, |
| 228 | operand: Inst.Ref, |
| 229 | }; |
| 230 | |
| 231 | pub const StrNode = struct { |
| 232 | src_node: NodeOffset, |
| 233 | string: NullTerminatedString, |
| 234 | }; |
| 235 | |
| 236 | pub const DataNode = struct { |
| 237 | src_node: NodeOffset, |
| 238 | data: u32, |
| 239 | }; |
| 240 | |
| 241 | pub const Inst = struct { |
| 242 | pub const List = std.MultiArrayList(Inst); |
| 243 | |
| 244 | tag: Tag, |
| 245 | data: EncodedData, |
| 246 | |
| 247 | /// The position of a Hir instruction within the `Hir` instructions array. |
| 248 | pub const Index = enum(u32) { |
| 249 | /// Hir is structured so that the outermost "main" struct of any file |
| 250 | /// is always at index 0. |
| 251 | main_module_inst = 0, |
| 252 | _, |
| 253 | |
| 254 | pub const static_len = @intFromEnum(Ref.empty_tuple) + 1; |
| 255 | |
| 256 | pub fn toRef(i: Index) Inst.Ref { |
| 257 | return @enumFromInt(static_len + @intFromEnum(i)); |
| 258 | } |
| 259 | |
| 260 | pub fn toOptional(i: Index) Optional { |
| 261 | return @enumFromInt(@intFromEnum(i)); |
| 262 | } |
| 263 | |
| 264 | pub fn op(i: Index, operand: Report.Operand) InstOp { |
| 265 | return .{ .index = i, .op = operand }; |
| 266 | } |
| 267 | |
| 268 | pub const Optional = enum(u32) { |
| 269 | none = std.math.maxInt(u32), |
| 270 | _, |
| 271 | |
| 272 | pub fn unwrap(oi: Optional) ?Index { |
| 273 | return if (oi == .none) null else @enumFromInt(@intFromEnum(oi)); |
| 274 | } |
| 275 | }; |
| 276 | }; |
| 277 | |
| 278 | /// A reference to an instruction, or to a static InternPool value. |
| 279 | /// |
| 280 | /// If the integer tag value is < InternPool.static_len, then it |
| 281 | /// corresponds to a Value. Otherwise, it is an instruction index. |
| 282 | pub const Ref = enum(u32) { |
| 283 | opaque_type, |
| 284 | bool_type, |
| 285 | int32_type, |
| 286 | float_type, |
| 287 | string_type, |
| 288 | never_type, |
| 289 | type_type, |
| 290 | numeric_literal_type, |
| 291 | numeric_literal_array_type, |
| 292 | never_array_type, |
| 293 | empty_tuple_type, |
| 294 | |
| 295 | void, |
| 296 | bool_true, |
| 297 | bool_false, |
| 298 | int32_zero, |
| 299 | float_zero, |
| 300 | nan, |
| 301 | empty_string, |
| 302 | empty_array, |
| 303 | empty_tuple, |
| 304 | _, |
| 305 | |
| 306 | pub const Decoded = union(enum) { |
| 307 | value: InternPool.Value, |
| 308 | index: Hir.Inst.Index, |
| 309 | }; |
| 310 | |
| 311 | pub fn decode(ref: Ref) Decoded { |
| 312 | const ref_int = @intFromEnum(ref); |
| 313 | return if (ref_int >= Index.static_len) |
| 314 | .{ .index = @enumFromInt(ref_int - Index.static_len) } |
| 315 | else |
| 316 | .{ .value = @enumFromInt(ref_int) }; |
| 317 | } |
| 318 | |
| 319 | pub fn index(ref: Ref) ?Index { |
| 320 | const ref_int = @intFromEnum(ref); |
| 321 | if (ref_int >= Index.static_len) { |
| 322 | return @enumFromInt(ref_int - Index.static_len); |
| 323 | } else { |
| 324 | return null; |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | pub fn value(inst: Ref) ?InternPool.Value { |
| 329 | const ref_int = @intFromEnum(inst); |
| 330 | |
| 331 | // TODO: is this > or >= |
| 332 | if (ref_int > Index.static_len) |
| 333 | return null; |
| 334 | |
| 335 | return @enumFromInt(ref_int); |
| 336 | } |
| 337 | |
| 338 | pub fn toOptional(ref: Ref) Optional { |
| 339 | return @enumFromInt(@intFromEnum(ref)); |
| 340 | } |
| 341 | |
| 342 | pub const Optional = enum(u32) { |
| 343 | none = std.math.maxInt(u32), |
| 344 | _, |
| 345 | |
| 346 | pub fn unwrap(opt: Optional) ?Ref { |
| 347 | return if (opt != .none) @enumFromInt(@intFromEnum(opt)) else null; |
| 348 | } |
| 349 | }; |
| 350 | }; |
| 351 | }; |
| 352 | |
| 353 | pub const Bin = struct { |
| 354 | lhs: Inst.Ref, |
| 355 | rhs: Inst.Ref, |
| 356 | }; |
| 357 | |
| 358 | pub const Store = struct { |
| 359 | memory: Inst.Index, |
| 360 | operand: Inst.Ref, |
| 361 | }; |
| 362 | |
| 363 | pub const StoreVarDecl = struct { |
| 364 | var_decl: NullTerminatedString, |
| 365 | operand: Inst.Ref, |
| 366 | }; |
| 367 | |
| 368 | pub const StoreField = struct { |
| 369 | base: Inst.Ref, |
| 370 | field: Inst.Ref, |
| 371 | operand: Inst.Ref, |
| 372 | }; |
| 373 | |
| 374 | pub const Coercion = struct { |
| 375 | type: Inst.Ref, |
| 376 | operand: Inst.Ref, |
| 377 | }; |
| 378 | |
| 379 | pub const Break = struct { |
| 380 | block: Inst.Index, |
| 381 | operand: Inst.Ref, |
| 382 | }; |
| 383 | |
| 384 | pub const Call = struct { |
| 385 | base: Inst.Ref, |
| 386 | args: RefList, |
| 387 | }; |
| 388 | |
| 389 | pub const If = struct { |
| 390 | cond: Inst.Ref, |
| 391 | yes: Inst.Ref, |
| 392 | no: Inst.Ref, |
| 393 | }; |
| 394 | |
| 395 | pub const JsNew = struct { |
| 396 | base: Inst.Ref, |
| 397 | args: RefList, |
| 398 | }; |
| 399 | |
| 400 | /// Index into extra which contains `field_len * 2` u32s. The first half |
| 401 | /// are the keys (NullTerminatedString) and the second half are field types |
| 402 | /// (Ref). This basically is like a specialized MultiArrayList. |
| 403 | pub const StructDecl = struct { |
| 404 | payload_index: u32, |
| 405 | field_len: u32, |
| 406 | |
| 407 | pub fn keys(decl: StructDecl, hir: *const Hir) []const NullTerminatedString { |
| 408 | return @ptrCast( |
| 409 | hir.extra.list.items[decl.payload_index..][0..decl.field_len], |
| 410 | ); |
| 411 | } |
| 412 | |
| 413 | pub fn types(decl: StructDecl, hir: *const Hir) []const Inst.Ref { |
| 414 | return @ptrCast( |
| 415 | hir.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len], |
| 416 | ); |
| 417 | } |
| 418 | }; |
| 419 | |
| 420 | /// This memory is laid out with the same strategy described in StructDecl |
| 421 | pub const StructInit = struct { |
| 422 | base_type: Inst.Ref, |
| 423 | payload_index: u32, |
| 424 | field_len: u32, |
| 425 | |
| 426 | pub fn keys(decl: StructInit, hir: *const Hir) []const NullTerminatedString { |
| 427 | return @ptrCast( |
| 428 | hir.extra.list.items[decl.payload_index..][0..decl.field_len], |
| 429 | ); |
| 430 | } |
| 431 | |
| 432 | pub fn inits(decl: StructInit, hir: *const Hir) []const Inst.Ref { |
| 433 | return @ptrCast( |
| 434 | hir.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len], |
| 435 | ); |
| 436 | } |
| 437 | }; |
| 438 | |
| 439 | pub const ArrayInit = struct { |
| 440 | /// Optionally provide a result type for more reliable type coercion. |
| 441 | /// If specified, this must be an array type. Otherwise, the first |
| 442 | /// element's type is used. |
| 443 | type_hint: Inst.Ref.Optional, |
| 444 | /// The items in the array |
| 445 | list: RefList, |
| 446 | }; |
| 447 | |
| 448 | pub const TupleType = struct { |
| 449 | /// The items in the tuple |
| 450 | items: RefList, |
| 451 | }; |
| 452 | |
| 453 | pub const TupleInit = struct { |
| 454 | /// Optionally provide a result type for more reliable type coercion. |
| 455 | /// If specified, this must be an array type. Otherwise, the first |
| 456 | /// element's type is used. |
| 457 | type_hint: Inst.Ref.Optional, |
| 458 | /// The items in the tuple |
| 459 | items: RefList, |
| 460 | }; |
| 461 | |
| 462 | pub const Decl = struct { |
| 463 | name: NullTerminatedString, |
| 464 | hash: Cache.BinDigest, |
| 465 | instruction_count: u32, |
| 466 | body: Inst.Ref, |
| 467 | }; |
| 468 | |
| 469 | pub const Func = struct { |
| 470 | params: OptionalRefList, |
| 471 | return_type: Inst.Ref.Optional, |
| 472 | body: Inst.Index, |
| 473 | captures: IndexList, |
| 474 | instruction_count: u32, |
| 475 | }; |
| 476 | |
| 477 | pub const ResultTypeArgument = struct { |
| 478 | /// The VALUE that is going to be called. |
| 479 | call_base: Inst.Ref, |
| 480 | /// Which argument |
| 481 | argument: u32, |
| 482 | }; |
| 483 | |
| 484 | pub const ResultTypeStructField = struct { |
| 485 | /// The type of the struct itself, |
| 486 | type: Inst.Ref, |
| 487 | /// Which field |
| 488 | field: NullTerminatedString, |
| 489 | }; |
| 490 | |
| 491 | pub const ResultTypeTupleField = struct { |
| 492 | /// The type of the tuple itself, |
| 493 | type: Inst.Ref, |
| 494 | /// Which item |
| 495 | item: u32, |
| 496 | }; |
| 497 | |
| 498 | pub const ResultTypeArrayValue = struct { |
| 499 | /// The type of the array itself, |
| 500 | type: Inst.Ref, |
| 501 | unused: u32 = 0, |
| 502 | }; |
| 503 | |
| 504 | // TODO: make this an enum |
| 505 | const NodeOffset = i32; |
| 506 | |
| 507 | // TODO: remove InstOp. LazyNode should not have a decl |
| 508 | pub const InstOp = struct { |
| 509 | index: Inst.Index, |
| 510 | op: Report.Operand, |
| 511 | |
| 512 | pub fn lazyNode(inst: InstOp, decl: Comp.Decl.Index) LazyNode { |
| 513 | return .{ |
| 514 | .inst = inst.index, |
| 515 | .operand = inst.op, |
| 516 | .decl = decl, |
| 517 | }; |
| 518 | } |
| 519 | }; |
| 520 | |
| 521 | pub const NullTerminatedString = enum(u32) { |
| 522 | empty = 0, |
| 523 | _, |
| 524 | |
| 525 | pub fn slice(str: NullTerminatedString, string_bytes: []const u8) [:0]const u8 { |
| 526 | if (str == .empty) return ""; |
| 527 | const sub = string_bytes[@intFromEnum(str)..]; |
| 528 | const i = mem.indexOfScalar(u8, sub, 0) orelse unreachable; |
| 529 | |
| 530 | return sub[0..i :0]; |
| 531 | } |
| 532 | }; |
| 533 | |
| 534 | pub const SlicedString = struct { |
| 535 | start: u32, |
| 536 | len: u32, |
| 537 | |
| 538 | pub fn slice(str: SlicedString, string_bytes: []const u8) []const u8 { |
| 539 | return string_bytes[str.start..][0..str.len]; |
| 540 | } |
| 541 | }; |
| 542 | |
| 543 | // TODO: Make the next three structs using a generic comptime thing |
| 544 | |
| 545 | pub const RefList = struct { |
| 546 | start: u32, |
| 547 | len: u32, |
| 548 | |
| 549 | pub fn slice(self: RefList, hir: *const Hir) []const Inst.Ref { |
| 550 | return @ptrCast(hir.extra.list.items[self.start..][0..self.len]); |
| 551 | } |
| 552 | }; |
| 553 | |
| 554 | pub const OptionalRefList = struct { |
| 555 | start: u32, |
| 556 | len: u32, |
| 557 | |
| 558 | pub fn slice(self: OptionalRefList, hir: *const Hir) []const Inst.Ref.Optional { |
| 559 | return @ptrCast(hir.extra.list.items[self.start..][0..self.len]); |
| 560 | } |
| 561 | }; |
| 562 | |
| 563 | pub const IndexList = struct { |
| 564 | start: u32, |
| 565 | len: u32, |
| 566 | |
| 567 | pub fn slice(self: IndexList, hir: *const Hir) []const Inst.Index { |
| 568 | return @ptrCast(hir.extra.list.items[self.start..][0..self.len]); |
| 569 | } |
| 570 | }; |
| 571 | |
| 572 | pub const NullTermStringList = struct { |
| 573 | start: u32, |
| 574 | len: u32, |
| 575 | |
| 576 | pub fn slice(self: IndexList, hir: *const Hir) []const NullTerminatedString { |
| 577 | return @ptrCast(hir.extra.list.items[self.start..][0..self.len]); |
| 578 | } |
| 579 | }; |
| 580 | |
| 581 | pub fn deinit(hir: *Hir, gpa: std.mem.Allocator) void { |
| 582 | gpa.free(hir.string_bytes); |
| 583 | hir.extra.deinit(gpa); |
| 584 | hir.insts.deinit(gpa); |
| 585 | hir.report.deinit(gpa); |
| 586 | hir.* = undefined; |
| 587 | } |
| 588 | |
| 589 | pub fn instTag(hir: *const Hir, index: Inst.Index) Hir.Tag { |
| 590 | return hir.insts.items(.tag)[@intFromEnum(index)]; |
| 591 | } |
| 592 | |
| 593 | pub fn instEncodedData(hir: *const Hir, index: Inst.Index) EncodedData { |
| 594 | return hir.insts.items(.data)[@intFromEnum(index)]; |
| 595 | } |
| 596 | |
| 597 | pub fn instData(hir: *const Hir, comptime tag: Hir.Tag, index: Inst.Index) tag.Data() { |
| 598 | return hir.instEncodedData(index).decode(tag, hir.instTag(index)); |
| 599 | } |
| 600 | |
| 601 | pub fn instDataUnsafe(hir: *const Hir, comptime T: type, index: Inst.Index) T { |
| 602 | return hir.instEncodedData(index).decodeUnsafe(T, hir.instTag(index)); |
| 603 | } |
| 604 | |
| 605 | pub fn instPayload(hir: *const Hir, comptime tag: Hir.Tag, index: Inst.Index) tag.Data().T { |
| 606 | return hir.instData(tag, index).extra_index.get(hir); |
| 607 | } |
| 608 | |
| 609 | pub fn instNodeOffset(hir: *const Hir, index: Inst.Index) i32 { |
| 610 | return switch (hir.instTag(index)) { |
| 611 | // these nodes never get errors, but Hir.Dump will print these regardless |
| 612 | // TODO: |
| 613 | .declaration, |
| 614 | .result_type_argument, |
| 615 | .result_type_struct_field, |
| 616 | .result_type_tuple_field, |
| 617 | .result_type_array_value, |
| 618 | => 0, |
| 619 | |
| 620 | inline else => |tag| hir.instData(tag, index).src_node, |
| 621 | }; |
| 622 | } |
| 623 | |
| 624 | pub fn blockInstructions(hir: *const Hir, index: Inst.Index) []const Inst.Index { |
| 625 | return hir.instData(.block, index).extra_index.get(hir).slice(hir); |
| 626 | } |
| 627 | |
| 628 | pub fn fullFuncDecl(hir: *const Hir, index: Inst.Index) struct { Hir.Decl, Hir.DeclFn } { |
| 629 | const decl, var trail = hir.instData(.declaration, index).extra_index.getTrail(hir); |
| 630 | assert(decl.kind == .function); |
| 631 | const decl_fn = trail.read(&hir.extra, Hir.DeclFn); |
| 632 | return .{ decl, decl_fn }; |
| 633 | } |
| 634 | |
| 635 | pub fn fullDecl(hir: *const Hir, index: Inst.Index) Decl { |
| 636 | const decl = hir.instEncodedData(index).decodeUnsafe(PlNode(Decl), hir.instTag(index)).extra_index.get(hir); |
| 637 | return .{ |
| 638 | .name = decl.name, |
| 639 | .hash = decl.hash, |
| 640 | .instruction_count = decl.instruction_count, |
| 641 | .body = decl.body, |
| 642 | }; |
| 643 | } |
| 644 | |
| 645 | pub fn declData(hir: *const Hir, index: Inst.Index) Hir.Decl { |
| 646 | return hir.instDataUnsafe(PlNode(Decl), index).extra_index.get(hir); |
| 647 | } |
| 648 | |
| 649 | pub fn moduleDeclarations(hir: *const Hir, module_index: Inst.Index) []const Inst.Index { |
| 650 | const module = hir.instData(.module, module_index); |
| 651 | const module_data: IndexList = module.extra_index.get(hir); |
| 652 | const slice = module_data.slice(hir); |
| 653 | if (core.runtime_safety) { |
| 654 | for (slice) |item| { |
| 655 | switch (hir.instTag(item)) { |
| 656 | .declaration, .variable_declaration, .test_declaration => {}, |
| 657 | else => { |
| 658 | std.debug.panic("\ngot unknown tag {s}\n", .{@tagName(hir.instTag(item))}); |
| 659 | }, |
| 660 | } |
| 661 | } |
| 662 | } |
| 663 | return slice; |
| 664 | } |
| 665 | |
| 666 | pub fn rootFunction(hir: *const Hir) Inst.Index { |
| 667 | const decls = hir.moduleDeclarations(.main_module_inst); |
| 668 | const root_fn = decls[decls.len - 1]; |
| 669 | assert(hir.declName(root_fn).len == 0); |
| 670 | return root_fn; |
| 671 | } |
| 672 | |
| 673 | pub fn declName(hir: *const Hir, decl_index: Inst.Index) []const u8 { |
| 674 | const decl = hir.instDataUnsafe(PlNode(Decl), decl_index); |
| 675 | const decl_data: Decl = decl.extra_index.get(hir); |
| 676 | return decl_data.name.slice(hir.string_bytes); |
| 677 | } |
| 678 | |
| 679 | pub fn dumpToWriter(hir: Hir, writer: anytype, tty: ?std.io.tty.Config) !void { |
| 680 | try writer.print("{}", .{Dump{ .hir = hir, .tty = tty }}); |
| 681 | } |
| 682 | |
| 683 | /// Used for generating decl hashes |
| 684 | fn isolatedRefValue(decl_index: Inst.Index, ref_to_hash: Inst.Ref) i32 { |
| 685 | return switch (ref_to_hash.decode()) { |
| 686 | .value => |value| -@as(i32, @intCast(@intFromEnum(value))), |
| 687 | .index => |index| @intCast(@intFromEnum(index) - @intFromEnum(decl_index)), |
| 688 | }; |
| 689 | } |
| 690 | |
| 691 | /// Used for generating decl hashes |
| 692 | fn isolatedIndexValue(decl_index: Inst.Index, index_to_hash: Inst.Index) u32 { |
| 693 | return @intFromEnum(index_to_hash) - @intFromEnum(decl_index); |
| 694 | } |
| 695 | |
| 696 | pub fn writeToHasher( |
| 697 | hir: *const Hir, |
| 698 | hasher: *HashHelper, |
| 699 | tag_list: []Tag, |
| 700 | data_list: []EncodedData, |
| 701 | decl_index: Inst.Index, |
| 702 | ) void { |
| 703 | // Add all of the tags |
| 704 | hasher.addBytes(std.mem.sliceAsBytes(tag_list)); |
| 705 | |
| 706 | // Add all of the data |
| 707 | for (tag_list, data_list) |tag, data| { |
| 708 | |
| 709 | // This should roughly line up with Hir.Tag.Data |
| 710 | switch (tag) { |
| 711 | // PlNode(IndexList), |
| 712 | .module, |
| 713 | .block, |
| 714 | .@"comptime", |
| 715 | => { |
| 716 | const pl_node = data.decodeUnsafe(PlNode(IndexList), tag); |
| 717 | hasher.add(pl_node.src_node); |
| 718 | writeIndexListToHasher(hasher, pl_node.extra_index.get(hir).slice(hir), decl_index); |
| 719 | }, |
| 720 | |
| 721 | // PlNode |
| 722 | inline .declaration, |
| 723 | .variable_declaration, |
| 724 | .test_declaration, |
| 725 | .coercion, |
| 726 | .@"break", |
| 727 | .number, |
| 728 | .string, |
| 729 | .call, |
| 730 | .@"if", |
| 731 | .store, |
| 732 | .js_new, |
| 733 | .struct_decl, |
| 734 | .struct_init, |
| 735 | .array_init, |
| 736 | .tuple_init, |
| 737 | .tuple_type, |
| 738 | .func, |
| 739 | .store_decl, |
| 740 | .store_field, |
| 741 | => |pl_node_tag| { |
| 742 | const pl_node = data.decode(pl_node_tag, tag); |
| 743 | hasher.add(pl_node.src_node); |
| 744 | hir.writePlNodeInnerToHasher(hasher, pl_node_tag.Data().T, pl_node.extra_index.get(hir), decl_index); |
| 745 | }, |
| 746 | // StrNode |
| 747 | .load_decl => { |
| 748 | const str_node = data.decodeUnsafe(StrNode, tag); |
| 749 | hasher.add(str_node.src_node); |
| 750 | hasher.addBytes(str_node.string.slice(hir.string_bytes)); |
| 751 | }, |
| 752 | // DataNode |
| 753 | .param => { |
| 754 | const data_node = data.decodeUnsafe(DataNode, tag); |
| 755 | hasher.add(data_node.src_node); |
| 756 | hasher.add(data_node.data); |
| 757 | }, |
| 758 | // RefNode |
| 759 | .memory, |
| 760 | .discard, |
| 761 | .ensure_void, |
| 762 | .negate, |
| 763 | .bool_not, |
| 764 | .bit_not, |
| 765 | .@"extern", |
| 766 | .import, |
| 767 | .fetch, |
| 768 | .compile_error, |
| 769 | .type_of, |
| 770 | .array_type, |
| 771 | .assert, |
| 772 | => { |
| 773 | const ref_node = data.decodeUnsafe(RefNode, tag); |
| 774 | hasher.add(ref_node.src_node); |
| 775 | hasher.add(isolatedRefValue(decl_index, ref_node.operand)); |
| 776 | }, |
| 777 | // PlNode(Bin) |
| 778 | .field, |
| 779 | .index, |
| 780 | .add, |
| 781 | .sub, |
| 782 | .mul, |
| 783 | .div, |
| 784 | .rem, |
| 785 | .pow, |
| 786 | .@"and", |
| 787 | .@"or", |
| 788 | .bit_and, |
| 789 | .bit_or, |
| 790 | .bit_xor, |
| 791 | .shl, |
| 792 | .shr, |
| 793 | .eq, |
| 794 | .ne, |
| 795 | .lt, |
| 796 | .le, |
| 797 | .gt, |
| 798 | .ge, |
| 799 | => { |
| 800 | const pl_node = data.decodeUnsafe(PlNode(Bin), tag); |
| 801 | const bin = pl_node.extra_index.get(hir); |
| 802 | hasher.add(isolatedRefValue(decl_index, bin.lhs)); |
| 803 | hasher.add(isolatedRefValue(decl_index, bin.rhs)); |
| 804 | }, |
| 805 | // Result Types |
| 806 | .result_type_argument => { |
| 807 | const arg = data.decodeUnsafe(ResultTypeArgument, tag); |
| 808 | hasher.add(isolatedRefValue(decl_index, arg.call_base)); |
| 809 | hasher.add(arg.argument); |
| 810 | }, |
| 811 | .result_type_struct_field => { |
| 812 | const struct_field = data.decodeUnsafe(ResultTypeStructField, tag); |
| 813 | hasher.add(isolatedRefValue(decl_index, struct_field.type)); |
| 814 | hasher.addBytes(struct_field.field.slice(hir.string_bytes)); |
| 815 | }, |
| 816 | .result_type_tuple_field => { |
| 817 | const tuple_field = data.decodeUnsafe(ResultTypeTupleField, tag); |
| 818 | hasher.add(isolatedRefValue(decl_index, tuple_field.type)); |
| 819 | hasher.add(tuple_field.item); |
| 820 | }, |
| 821 | .result_type_array_value => { |
| 822 | const array_value = data.decodeUnsafe(ResultTypeArrayValue, tag); |
| 823 | hasher.add(isolatedRefValue(decl_index, array_value.type)); |
| 824 | }, |
| 825 | } |
| 826 | } |
| 827 | } |
| 828 | |
| 829 | fn writeIndexListToHasher( |
| 830 | hasher: *HashHelper, |
| 831 | index_list: []const Inst.Index, |
| 832 | decl_index: Inst.Index, |
| 833 | ) void { |
| 834 | hasher.add(index_list.len); |
| 835 | for (index_list) |idx| { |
| 836 | hasher.add(isolatedIndexValue(decl_index, idx)); |
| 837 | } |
| 838 | } |
| 839 | |
| 840 | fn writeRefListToHasher( |
| 841 | hasher: *HashHelper, |
| 842 | decl_index: Inst.Index, |
| 843 | index_list: []const Inst.Ref, |
| 844 | ) void { |
| 845 | hasher.add(index_list.len); |
| 846 | for (index_list) |idx| { |
| 847 | hasher.add(isolatedRefValue(decl_index, idx)); |
| 848 | } |
| 849 | } |
| 850 | |
| 851 | fn writePlNodeInnerToHasher( |
| 852 | hir: *const Hir, |
| 853 | hasher: *HashHelper, |
| 854 | comptime T: type, |
| 855 | data: T, |
| 856 | decl_index: Inst.Index, |
| 857 | ) void { |
| 858 | switch (T) { |
| 859 | Decl => { |
| 860 | hasher.addBytes(&data.hash); |
| 861 | hasher.addBytes(data.name.slice(hir.string_bytes)); |
| 862 | }, |
| 863 | Float => { |
| 864 | const as_bytes: [2]u32 = .{ |
| 865 | data.low, |
| 866 | data.high, |
| 867 | }; |
| 868 | hasher.add(@as(u64, @bitCast(as_bytes))); |
| 869 | }, |
| 870 | SlicedString => { |
| 871 | hasher.addBytes(data.slice(hir.string_bytes)); |
| 872 | }, |
| 873 | Coercion => { |
| 874 | hasher.add(isolatedRefValue(decl_index, data.type)); |
| 875 | hasher.add(isolatedRefValue(decl_index, data.operand)); |
| 876 | }, |
| 877 | Break => { |
| 878 | hasher.add(isolatedIndexValue(decl_index, data.block)); |
| 879 | hasher.add(isolatedRefValue(decl_index, data.operand)); |
| 880 | }, |
| 881 | Call => { |
| 882 | hasher.add(isolatedRefValue(decl_index, data.base)); |
| 883 | writeRefListToHasher(hasher, decl_index, data.args.slice(hir)); |
| 884 | }, |
| 885 | If => { |
| 886 | hasher.add(isolatedRefValue(decl_index, data.cond)); |
| 887 | hasher.add(isolatedRefValue(decl_index, data.yes)); |
| 888 | hasher.add(isolatedRefValue(decl_index, data.no)); |
| 889 | }, |
| 890 | Store => { |
| 891 | hasher.add(isolatedIndexValue(decl_index, data.memory)); |
| 892 | hasher.add(isolatedRefValue(decl_index, data.operand)); |
| 893 | }, |
| 894 | StoreField => { |
| 895 | hasher.add(isolatedRefValue(decl_index, data.base)); |
| 896 | hasher.add(isolatedRefValue(decl_index, data.field)); |
| 897 | hasher.add(isolatedRefValue(decl_index, data.operand)); |
| 898 | }, |
| 899 | StoreVarDecl => { |
| 900 | hasher.addBytes(data.var_decl.slice(hir.string_bytes)); |
| 901 | hasher.add(isolatedRefValue(decl_index, data.operand)); |
| 902 | }, |
| 903 | JsNew => { |
| 904 | hasher.add(isolatedRefValue(decl_index, data.base)); |
| 905 | writeRefListToHasher(hasher, decl_index, data.args.slice(hir)); |
| 906 | }, |
| 907 | StructDecl => { |
| 908 | hasher.add(data.field_len); |
| 909 | for (data.keys(hir), data.types(hir)) |key, ty| { |
| 910 | hasher.addBytes(key.slice(hir.string_bytes)); |
| 911 | hasher.add(isolatedRefValue(decl_index, ty)); |
| 912 | } |
| 913 | }, |
| 914 | StructInit => { |
| 915 | hasher.add(isolatedRefValue(decl_index, data.base_type)); |
| 916 | hasher.add(data.field_len); |
| 917 | for (data.keys(hir), data.inits(hir)) |key, init| { |
| 918 | hasher.addBytes(key.slice(hir.string_bytes)); |
| 919 | hasher.add(isolatedRefValue(decl_index, init)); |
| 920 | } |
| 921 | }, |
| 922 | ArrayInit => { |
| 923 | if (data.type_hint.unwrap()) |type_hint| |
| 924 | hasher.add(isolatedRefValue(decl_index, type_hint)) |
| 925 | else |
| 926 | hasher.add(@as(i32, std.math.maxInt(i32))); |
| 927 | writeRefListToHasher(hasher, decl_index, data.list.slice(hir)); |
| 928 | }, |
| 929 | Func => { |
| 930 | for (data.params.slice(hir)) |optional| { |
| 931 | if (optional.unwrap()) |item| |
| 932 | hasher.add(isolatedRefValue(decl_index, item)); |
| 933 | } |
| 934 | |
| 935 | if (data.return_type.unwrap()) |type_hint| |
| 936 | hasher.add(isolatedRefValue(decl_index, type_hint)) |
| 937 | else |
| 938 | hasher.add(@as(i32, std.math.maxInt(i32))); |
| 939 | |
| 940 | hasher.add(isolatedIndexValue(decl_index, data.body)); |
| 941 | hasher.add(data.instruction_count); |
| 942 | }, |
| 943 | TupleType => { |
| 944 | hasher.add(data.items.len); |
| 945 | for (data.items.slice(hir)) |item| { |
| 946 | hasher.add(isolatedRefValue(decl_index, item)); |
| 947 | } |
| 948 | }, |
| 949 | TupleInit => { |
| 950 | if (data.type_hint.unwrap()) |type_hint| |
| 951 | hasher.add(isolatedRefValue(decl_index, type_hint)) |
| 952 | else |
| 953 | hasher.add(@as(i32, std.math.maxInt(i32))); |
| 954 | writeRefListToHasher(hasher, decl_index, data.items.slice(hir)); |
| 955 | }, |
| 956 | else => @compileError("hash not implemented for " ++ @typeName(T)), |
| 957 | } |
| 958 | } |
| 959 | |
| 960 | pub const SerializedHeader = extern struct { |
| 961 | const version = @import("config").version_string ++ "\x00"; |
| 962 | |
| 963 | instruction_len: u32, |
| 964 | string_bytes: u32, |
| 965 | extra_len: u32, |
| 966 | report_err_len: u32, |
| 967 | report_warn_len: u32, |
| 968 | }; |
| 969 | |
| 970 | pub fn serialize(hir: Hir, target: core.SerializeTarget) !void { |
| 971 | const header: SerializedHeader = .{ |
| 972 | .instruction_len = @intCast(hir.insts.len), |
| 973 | .string_bytes = @intCast(hir.string_bytes.len), |
| 974 | .extra_len = @intCast(hir.extra.list.items.len), |
| 975 | .report_err_len = @intCast(hir.report.errors.len), |
| 976 | .report_warn_len = @intCast(hir.report.warnings.len), |
| 977 | }; |
| 978 | |
| 979 | var iovecs = [_]std.posix.iovec_const{ |
| 980 | core.iovecConst(SerializedHeader.version), |
| 981 | core.iovecConst(std.mem.asBytes(&header)), |
| 982 | core.iovecConst(std.mem.sliceAsBytes(hir.insts.items(.tag))), |
| 983 | core.iovecConst(std.mem.sliceAsBytes(hir.insts.items(.data))), |
| 984 | core.iovecConst(hir.string_bytes), |
| 985 | core.iovecConst(std.mem.sliceAsBytes(hir.extra.list.items)), |
| 986 | core.iovecConst(std.mem.sliceAsBytes(hir.report.errors)), |
| 987 | core.iovecConst(std.mem.sliceAsBytes(hir.report.warnings)), |
| 988 | }; |
| 989 | try target.writev(&iovecs); |
| 990 | } |
| 991 | |
| 992 | const Hir = @This(); |
| 993 | |
| 994 | const std = @import("std"); |
| 995 | const mem = std.mem; |
| 996 | const assert = std.debug.assert; |
| 997 | const HashHelper = std.Build.Cache.HashHelper; |
| 998 | |
| 999 | const core = @import("mod.zig"); |
| 1000 | const Cache = core.Cache; |
| 1001 | |
| 1002 | const Ast = core.Ast; |
| 1003 | const Unused = Ast.Unused; |
| 1004 | const NodeIndex = Ast.NodeIndex; |
| 1005 | const TokenIndex = Ast.TokenIndex; |
| 1006 | const OptionalNodeIndex = Ast.OptionalNodeIndex; |
| 1007 | const OptionalTokenIndex = Ast.OptionalTokenIndex; |
| 1008 | const OptionalExtraIndex = Ast.OptionalExtraIndex; |
| 1009 | |
| 1010 | const dod = core.array; |
| 1011 | const Float = dod.Float; |
| 1012 | |
| 1013 | const InternPool = core.InternPool; |
| 1014 | |
| 1015 | const Air = core.Air; |
| 1016 | |
| 1017 | const Comp = core.Comp; |
| 1018 | |
| 1019 | const Report = core.Report; |
| 1020 | const LazyNode = Report.LazyNode; |