| author | |
| committer | |
| log | fd9bc5492894391c36ce4ee485e103ceb0255f76 |
| tree | afb74799a604f6c032ed06791f284a138b15ce67 |
| parent | 230648717c1f197ece3aec476cda99ad3fa158a6 |
array out of bounds should raise an unrecoverable error. this will also
happen for addition out of bounds, runtime assertions, and other things.
safety checks are generated Air instructions using existing constructs
like `if`, with a new instruction tag: `panic` which is intended to
raise an unrecoverable error. using air lets these checks lower to any
backend without additional work, as well as take advantage of existing
and future optimizations.
this also adds `bool_and` and `bool_or` in the Transform pass, but these
generate `if` instructions to re-use logic for short circuiting.
the js backend can't actually make an unrecoverable error. since try
catch blocks are banned, this is fine. in a node.js target, NodeGen
could make this use `process.exit`. for wasm and native, this can lower
to a log combined with a trap instruction.
18 files changed, 459 insertions(+), 116 deletions(-)
CHANGELOG.md+3| ... | @@ -2,11 +2,14 @@ | ... | @@ -2,11 +2,14 @@ |
| 2 | 2 | ||
| 3 | - chore: rename the language to "ix" | 3 | - chore: rename the language to "ix" |
| 4 | - feat: language manual gets a pleasing redesign | 4 | - feat: language manual gets a pleasing redesign |
| 5 | - feat: add boolean `and` and `or` operator keywords | ||
| 5 | - feat: allow any expression as a function body | 6 | - feat: allow any expression as a function body |
| 6 | - feat: add `assert` keyword | 7 | - feat: add `assert` keyword |
| 8 | - feat: add safety checks for array out of bounds | ||
| 7 | - fix: coerce `break` operands and implicit breaks to result type | 9 | - fix: coerce `break` operands and implicit breaks to result type |
| 8 | - fix: coerce results when `if` expression branches merge | 10 | - fix: coerce results when `if` expression branches merge |
| 9 | - fix: ensure if branch results to a runtime value when the condition is not compile-time known | 11 | - fix: ensure if branch results to a runtime value when the condition is not compile-time known |
| 12 | - fix: inferred array type uses item type for items instead of the container type. | ||
| 10 | - fix(js): stop printing redundant parenthesis on ternary expressions | 13 | - fix(js): stop printing redundant parenthesis on ternary expressions |
| 11 | 14 | ||
| 12 | ## 0.1.6 | 15 | ## 0.1.6 |
build.zig+2-1| ... | @@ -594,7 +594,8 @@ fn crossTarget(cpu_arch: std.Target.Cpu.Arch, os_tag: std.Target.Os.Tag) Build.R | ... | @@ -594,7 +594,8 @@ fn crossTarget(cpu_arch: std.Target.Cpu.Arch, os_tag: std.Target.Os.Tag) Build.R |
| 594 | }; | 594 | }; |
| 595 | return .{ | 595 | return .{ |
| 596 | .query = query, | 596 | .query = query, |
| 597 | .result = std.zig.system.resolveTargetQuery(query) catch unreachable, | 597 | .result = std.zig.system.resolveTargetQuery(query) catch |
| 598 | @panic("unable to resolve target query"), | ||
| 598 | }; | 599 | }; |
| 599 | } | 600 | } |
| 600 | 601 |
build.zig.zon+1-1| ... | @@ -2,7 +2,7 @@ | ... | @@ -2,7 +2,7 @@ |
| 2 | .name = "", | 2 | .name = "", |
| 3 | .version = "0.0.0", | 3 | .version = "0.0.0", |
| 4 | 4 | ||
| 5 | .minimum_zig_version = "0.14.0-dev.1511+54b668f8a", | 5 | .minimum_zig_version = "0.14.0-dev.2034+56996a280", |
| 6 | 6 | ||
| 7 | .dependencies = .{ | 7 | .dependencies = .{ |
| 8 | // Used to determine unicode properties to tokenize identifiers | 8 | // Used to determine unicode properties to tokenize identifiers |
src/core/Air.Dump.zig+3| ... | @@ -87,6 +87,8 @@ pub const State = struct { | ... | @@ -87,6 +87,8 @@ pub const State = struct { |
| 87 | .lt, | 87 | .lt, |
| 88 | .gt, | 88 | .gt, |
| 89 | .eq, | 89 | .eq, |
| 90 | .le, | ||
| 91 | .ge, | ||
| 90 | .field_array, | 92 | .field_array, |
| 91 | .field_opaque, | 93 | .field_opaque, |
| 92 | => |comptime_tag| { | 94 | => |comptime_tag| { |
| ... | @@ -100,6 +102,7 @@ pub const State = struct { | ... | @@ -100,6 +102,7 @@ pub const State = struct { |
| 100 | inline .negate, | 102 | inline .negate, |
| 101 | .bool_not, | 103 | .bool_not, |
| 102 | .array_len, | 104 | .array_len, |
| 105 | .panic, | ||
| 103 | => |comptime_tag| { | 106 | => |comptime_tag| { |
| 104 | const bin: Air.Un = air.instData(comptime_tag, inst); | 107 | const bin: Air.Un = air.instData(comptime_tag, inst); |
| 105 | try w.writeAll("("); | 108 | try w.writeAll("("); |
src/core/Air.zig+10-6| ... | @@ -34,20 +34,19 @@ pub const Inst = struct { | ... | @@ -34,20 +34,19 @@ pub const Inst = struct { |
| 34 | 34 | ||
| 35 | array_len, | 35 | array_len, |
| 36 | 36 | ||
| 37 | // bin | ||
| 38 | add, | 37 | add, |
| 39 | sub, | 38 | sub, |
| 40 | mul, | 39 | mul, |
| 41 | div, | 40 | div, |
| 42 | |||
| 43 | eq, | 41 | eq, |
| 44 | |||
| 45 | lt, | 42 | lt, |
| 43 | le, | ||
| 46 | gt, | 44 | gt, |
| 45 | ge, | ||
| 47 | 46 | ||
| 48 | // un | ||
| 49 | negate, | 47 | negate, |
| 50 | bool_not, | 48 | bool_not, |
| 49 | panic, | ||
| 51 | 50 | ||
| 52 | pub fn Data(comptime tag: Tag) type { | 51 | pub fn Data(comptime tag: Tag) type { |
| 53 | return switch (tag) { | 52 | return switch (tag) { |
| ... | @@ -71,10 +70,13 @@ pub const Inst = struct { | ... | @@ -71,10 +70,13 @@ pub const Inst = struct { |
| 71 | .lt, | 70 | .lt, |
| 72 | .gt, | 71 | .gt, |
| 73 | .eq, | 72 | .eq, |
| 73 | .le, | ||
| 74 | .ge, | ||
| 74 | => Bin, | 75 | => Bin, |
| 75 | .negate, | 76 | .negate, |
| 76 | .bool_not, | 77 | .bool_not, |
| 77 | .array_len, | 78 | .array_len, |
| 79 | .panic, | ||
| 78 | => Un, | 80 | => Un, |
| 79 | }; | 81 | }; |
| 80 | } | 82 | } |
| ... | @@ -85,6 +87,8 @@ pub const Inst = struct { | ... | @@ -85,6 +87,8 @@ pub const Inst = struct { |
| 85 | kind: Kind, | 87 | kind: Kind, |
| 86 | data: u31, | 88 | data: u31, |
| 87 | 89 | ||
| 90 | pub const @"void" = Value.void.toAir(); | ||
| 91 | |||
| 88 | pub const Kind = enum(u1) { value, index }; | 92 | pub const Kind = enum(u1) { value, index }; |
| 89 | 93 | ||
| 90 | pub const Decoded = union(enum) { | 94 | pub const Decoded = union(enum) { |
| ... | @@ -345,8 +349,8 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In | ... | @@ -345,8 +349,8 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In |
| 345 | .js_new => .@"opaque", | 349 | .js_new => .@"opaque", |
| 346 | 350 | ||
| 347 | // These operations always produce known types | 351 | // These operations always produce known types |
| 348 | .lt, .gt, .eq, .bool_not => .bool, | 352 | .lt, .gt, .le, .ge, .eq, .bool_not => .bool, |
| 349 | .@"break" => .never, | 353 | .@"break", .panic => .never, |
| 350 | .field_opaque => .@"opaque", | 354 | .field_opaque => .@"opaque", |
| 351 | .array_len => .int32, | 355 | .array_len => .int32, |
| 352 | 356 |
src/core/Ast.zig+6-6| ... | @@ -144,9 +144,9 @@ pub const Node = struct { | ... | @@ -144,9 +144,9 @@ pub const Node = struct { |
| 144 | /// `lhs ** rhs`. main_token is the operator. | 144 | /// `lhs ** rhs`. main_token is the operator. |
| 145 | pow, | 145 | pow, |
| 146 | /// `lhs && rhs`. main_token is the operator. | 146 | /// `lhs && rhs`. main_token is the operator. |
| 147 | @"and", | 147 | bool_and, |
| 148 | /// `lhs || rhs`. main_token is the operator. | 148 | /// `lhs || rhs`. main_token is the operator. |
| 149 | @"or", | 149 | bool_or, |
| 150 | /// `lhs & rhs`. main_token is the operator. | 150 | /// `lhs & rhs`. main_token is the operator. |
| 151 | bit_and, | 151 | bit_and, |
| 152 | /// `lhs | rhs`. main_token is the operator. | 152 | /// `lhs | rhs`. main_token is the operator. |
| ... | @@ -247,8 +247,8 @@ pub const Node = struct { | ... | @@ -247,8 +247,8 @@ pub const Node = struct { |
| 247 | .div, | 247 | .div, |
| 248 | .rem, | 248 | .rem, |
| 249 | .pow, | 249 | .pow, |
| 250 | .@"and", | 250 | .bool_and, |
| 251 | .@"or", | 251 | .bool_or, |
| 252 | .bit_and, | 252 | .bit_and, |
| 253 | .bit_or, | 253 | .bit_or, |
| 254 | .bit_xor, | 254 | .bit_xor, |
| ... | @@ -280,11 +280,11 @@ pub const Node = struct { | ... | @@ -280,11 +280,11 @@ pub const Node = struct { |
| 280 | 280 | ||
| 281 | pub const data_tags = std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{ | 281 | pub const data_tags = std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{ |
| 282 | // sort-lines:start | 282 | // sort-lines:start |
| 283 | .@"and" = .bin, | 283 | .bool_and = .bin, |
| 284 | .@"break" = .@"break", | 284 | .@"break" = .@"break", |
| 285 | .@"comptime" = .un, | 285 | .@"comptime" = .un, |
| 286 | .@"if" = .@"if", | 286 | .@"if" = .@"if", |
| 287 | .@"or" = .bin, | 287 | .bool_or = .bin, |
| 288 | .@"return" = .@"return", | 288 | .@"return" = .@"return", |
| 289 | .add = .bin, | 289 | .add = .bin, |
| 290 | .add_assign = .bin, | 290 | .add_assign = .bin, |
src/core/Hir.Dump.zig+1-1| ... | @@ -307,7 +307,7 @@ const State = struct { | ... | @@ -307,7 +307,7 @@ const State = struct { |
| 307 | 307 | ||
| 308 | if (data.type_hint.unwrap()) |type_hint| { | 308 | if (data.type_hint.unwrap()) |type_hint| { |
| 309 | try writer.writeAll("("); | 309 | try writer.writeAll("("); |
| 310 | try s.dumpRef(writer, type_hint); | 310 | try s.dumpRefMarker(writer, type_hint); |
| 311 | try writer.writeAll(", "); | 311 | try writer.writeAll(", "); |
| 312 | } | 312 | } |
| 313 | 313 |
src/core/Hir.zig+5-2| ... | @@ -60,9 +60,12 @@ pub const Tag = enum(u8) { | ... | @@ -60,9 +60,12 @@ pub const Tag = enum(u8) { |
| 60 | /// Asserts the operand is `void`. Used for undiscarded expressions. | 60 | /// Asserts the operand is `void`. Used for undiscarded expressions. |
| 61 | ensure_void, | 61 | ensure_void, |
| 62 | 62 | ||
| 63 | /// Branch takes a boolean condition and takes either a yes or no path. | 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. | ||
| 64 | @"if", | 66 | @"if", |
| 65 | /// Given a block instruction | 67 | /// Given a block instruction, evaluate to a Value without generating |
| 68 | /// any runtime instructions. | ||
| 66 | @"comptime", | 69 | @"comptime", |
| 67 | 70 | ||
| 68 | /// A function expression, with or without captures | 71 | /// A function expression, with or without captures |
src/core/Parse.zig+15-14| ... | @@ -111,8 +111,8 @@ const Operator = enum(u8) { | ... | @@ -111,8 +111,8 @@ const Operator = enum(u8) { |
| 111 | div = @intFromEnum(Token.Tag.slash), | 111 | div = @intFromEnum(Token.Tag.slash), |
| 112 | rem = @intFromEnum(Token.Tag.percent), | 112 | rem = @intFromEnum(Token.Tag.percent), |
| 113 | pow = @intFromEnum(Token.Tag.star_star), | 113 | pow = @intFromEnum(Token.Tag.star_star), |
| 114 | @"and" = @intFromEnum(Token.Tag.and_and), | 114 | bool_and = @intFromEnum(Token.Tag.kw_and), |
| 115 | @"or" = @intFromEnum(Token.Tag.pipe_pipe), | 115 | bool_or = @intFromEnum(Token.Tag.kw_or), |
| 116 | bit_and = @intFromEnum(Token.Tag.@"and"), | 116 | bit_and = @intFromEnum(Token.Tag.@"and"), |
| 117 | bit_or = @intFromEnum(Token.Tag.pipe), | 117 | bit_or = @intFromEnum(Token.Tag.pipe), |
| 118 | bit_xor = @intFromEnum(Token.Tag.caret), | 118 | bit_xor = @intFromEnum(Token.Tag.caret), |
| ... | @@ -209,8 +209,8 @@ const Operator = enum(u8) { | ... | @@ -209,8 +209,8 @@ const Operator = enum(u8) { |
| 209 | .shl, .shr => .bitwise_shift, | 209 | .shl, .shr => .bitwise_shift, |
| 210 | .lt, .le, .gt, .ge => .relational, | 210 | .lt, .le, .gt, .ge => .relational, |
| 211 | .eq, .ne => .equality, | 211 | .eq, .ne => .equality, |
| 212 | .@"and" => .logical_and, | 212 | .bool_and => .logical_and, |
| 213 | .@"or" => .logical_or, | 213 | .bool_or => .logical_or, |
| 214 | .bit_and => .bit_and, | 214 | .bit_and => .bit_and, |
| 215 | .bit_xor => .bit_xor, | 215 | .bit_xor => .bit_xor, |
| 216 | .bit_or => .bit_or, | 216 | .bit_or => .bit_or, |
| ... | @@ -1493,14 +1493,15 @@ const Comp = core.Comp; | ... | @@ -1493,14 +1493,15 @@ const Comp = core.Comp; |
| 1493 | 1493 | ||
| 1494 | test "fuzz test parser for crashes" { | 1494 | test "fuzz test parser for crashes" { |
| 1495 | try core.unicode_props.init(); | 1495 | try core.unicode_props.init(); |
| 1496 | 1496 | try std.testing.fuzz(struct { | |
| 1497 | const input_bytes = std.testing.fuzzInput(.{}); | 1497 | fn run(input_bytes: []const u8) !void { |
| 1498 | 1498 | const sentinel = try std.testing.allocator.dupeZ(u8, input_bytes); | |
| 1499 | const sentinel = try std.testing.allocator.dupeZ(u8, input_bytes); | 1499 | defer std.testing.allocator.free(sentinel); |
| 1500 | defer std.testing.allocator.free(sentinel); | 1500 | |
| 1501 | 1501 | var err: AstError.WithTrace = undefined; | |
| 1502 | var err: AstError.WithTrace = undefined; | 1502 | var ast = parse(std.testing.allocator, sentinel, &err) catch |
| 1503 | var ast = parse(std.testing.allocator, sentinel, &err) catch | 1503 | return; // ignore parser errors, only fail on crashes |
| 1504 | return; // ignore parser errors, only fail on crashes | 1504 | ast.deinit(std.testing.allocator); |
| 1505 | ast.deinit(std.testing.allocator); | 1505 | } |
| 1506 | }.run, .{}); | ||
| 1506 | } | 1507 | } |
src/core/Sema.zig+153-38| ... | @@ -13,6 +13,8 @@ arena_state: std.heap.ArenaAllocator, | ... | @@ -13,6 +13,8 @@ arena_state: std.heap.ArenaAllocator, |
| 13 | /// When this is .decl, we are producing a `Value` | 13 | /// When this is .decl, we are producing a `Value` |
| 14 | owner: AnalysisUnit, | 14 | owner: AnalysisUnit, |
| 15 | 15 | ||
| 16 | generate_safety_checks: bool = true, | ||
| 17 | |||
| 16 | /// The namespace index that is used for declaration lookups | 18 | /// The namespace index that is used for declaration lookups |
| 17 | ns_index: Namespace.Index, | 19 | ns_index: Namespace.Index, |
| 18 | /// Non-owning copy | 20 | /// Non-owning copy |
| ... | @@ -358,8 +360,9 @@ fn markInst(s: *Sema, inst: Air.Inst.Index) void { | ... | @@ -358,8 +360,9 @@ fn markInst(s: *Sema, inst: Air.Inst.Index) void { |
| 358 | flags.uses = flags.uses.increment(); | 360 | flags.uses = flags.uses.increment(); |
| 359 | } | 361 | } |
| 360 | 362 | ||
| 361 | // TODO: name when awaake pls | 363 | const LivenessMark = enum { |
| 362 | const DoMark = enum { | 364 | /// The value retrieved will not be used as the operand for another |
| 365 | /// instruction. | ||
| 363 | no_mark, | 366 | no_mark, |
| 364 | /// A Ref must be marked if it's value is used as the operand for another | 367 | /// A Ref must be marked if it's value is used as the operand for another |
| 365 | /// Air instruction. This informs the Js backend if an expression must | 368 | /// Air instruction. This informs the Js backend if an expression must |
| ... | @@ -367,7 +370,7 @@ const DoMark = enum { | ... | @@ -367,7 +370,7 @@ const DoMark = enum { |
| 367 | marked, | 370 | marked, |
| 368 | }; | 371 | }; |
| 369 | 372 | ||
| 370 | fn getRef(s: *Sema, teg_ref: Hir.Inst.Ref, mark: DoMark) Air.Inst.Ref { | 373 | fn getRef(s: *Sema, teg_ref: Hir.Inst.Ref, mark: LivenessMark) Air.Inst.Ref { |
| 371 | switch (teg_ref.decode()) { | 374 | switch (teg_ref.decode()) { |
| 372 | .value => |value| return value.toAir(), | 375 | .value => |value| return value.toAir(), |
| 373 | .index => |index| { | 376 | .index => |index| { |
| ... | @@ -400,7 +403,7 @@ fn getCoercedValue(s: *Sema, src: LazyNode, teg_ref: Hir.Inst.Ref, ty: Type) !Va | ... | @@ -400,7 +403,7 @@ fn getCoercedValue(s: *Sema, src: LazyNode, teg_ref: Hir.Inst.Ref, ty: Type) !Va |
| 400 | return try s.coerceValue(src, any, ty); | 403 | return try s.coerceValue(src, any, ty); |
| 401 | } | 404 | } |
| 402 | 405 | ||
| 403 | fn getCoercedRef(s: *Sema, src: LazyNode, teg_ref: Hir.Inst.Ref, ty: Type, mark: DoMark) !Air.Inst.Ref { | 406 | fn getCoercedRef(s: *Sema, src: LazyNode, teg_ref: Hir.Inst.Ref, ty: Type, mark: LivenessMark) !Air.Inst.Ref { |
| 404 | const any = s.getRef(teg_ref, mark); | 407 | const any = s.getRef(teg_ref, mark); |
| 405 | return try s.coerceRef(src, any, ty); | 408 | return try s.coerceRef(src, any, ty); |
| 406 | } | 409 | } |
| ... | @@ -619,6 +622,8 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -619,6 +622,8 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 619 | }); | 622 | }); |
| 620 | } | 623 | } |
| 621 | 624 | ||
| 625 | try s.ensureAtRuntime(s.lazyNode(.operand)); | ||
| 626 | |||
| 622 | return s.addRef(.negate, .{ .operand = operand }); | 627 | return s.addRef(.negate, .{ .operand = operand }); |
| 623 | }, | 628 | }, |
| 624 | .bool_not => { | 629 | .bool_not => { |
| ... | @@ -799,10 +804,13 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -799,10 +804,13 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 799 | const scratch = try s.scratch.reserve(s.arena(), Air.Inst.Ref, node.list.len); | 804 | const scratch = try s.scratch.reserve(s.arena(), Air.Inst.Ref, node.list.len); |
| 800 | defer scratch.deinit(); | 805 | defer scratch.deinit(); |
| 801 | 806 | ||
| 802 | var item_type: ?Type = if (node.type_hint.unwrap()) |ty| | 807 | var item_type: ?Type = if (node.type_hint.unwrap()) |ty| inferred_type: { |
| 803 | try s.getType(s.lazyNode(.operand), ty) | 808 | const parent_type = try s.getType(s.lazyNode(.operand), ty); |
| 804 | else | 809 | const array_data: InternPool.Key.ArrayType = parent_type.toArrayType(s.ip) orelse |
| 805 | null; | 810 | // Allow the coercion to fail later. |
| 811 | break :inferred_type null; | ||
| 812 | break :inferred_type array_data.item_type; | ||
| 813 | } else null; | ||
| 806 | 814 | ||
| 807 | var is_constant = true; | 815 | var is_constant = true; |
| 808 | 816 | ||
| ... | @@ -1103,13 +1111,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1103,13 +1111,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1103 | 1111 | ||
| 1104 | switch (data.conditional) { | 1112 | switch (data.conditional) { |
| 1105 | .comptime_condition => { | 1113 | .comptime_condition => { |
| 1106 | assert(operand.kind == .value); // must be comptime | ||
| 1107 | s.inst_map.add(s.frame.currentIndex(), operand); | 1114 | s.inst_map.add(s.frame.currentIndex(), operand); |
| 1108 | s.frame.advance(); | 1115 | s.frame.advance(); |
| 1109 | return .@"continue"; | 1116 | return .@"continue"; |
| 1110 | }, | 1117 | }, |
| 1111 | .runtime_yes => { | 1118 | .runtime_yes => { |
| 1112 | try s.emitRuntimeBlock(if_data.yes.decode().index, operand, mark); | 1119 | try s.emitRuntimeBranchForIf(if_data.yes.decode().index, operand, mark); |
| 1113 | mark.deinit(); | 1120 | mark.deinit(); |
| 1114 | 1121 | ||
| 1115 | switch (if_data.no.decode()) { | 1122 | switch (if_data.no.decode()) { |
| ... | @@ -1127,7 +1134,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1127,7 +1134,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1127 | } | 1134 | } |
| 1128 | }, | 1135 | }, |
| 1129 | .runtime_no => { | 1136 | .runtime_no => { |
| 1130 | try s.emitRuntimeBlock(if_data.no.decode().index, operand, mark); | 1137 | try s.emitRuntimeBranchForIf(if_data.no.decode().index, operand, mark); |
| 1131 | mark.deinit(); | 1138 | mark.deinit(); |
| 1132 | }, | 1139 | }, |
| 1133 | } | 1140 | } |
| ... | @@ -1140,6 +1147,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1140,6 +1147,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1140 | if (yes.value()) |val| yes = (try s.ensureValueRuntime(s.lazyNode(.lhs), val)).toAir(); | 1147 | if (yes.value()) |val| yes = (try s.ensureValueRuntime(s.lazyNode(.lhs), val)).toAir(); |
| 1141 | if (no.value()) |val| no = (try s.ensureValueRuntime(s.lazyNode(.rhs), val)).toAir(); | 1148 | if (no.value()) |val| no = (try s.ensureValueRuntime(s.lazyNode(.rhs), val)).toAir(); |
| 1142 | 1149 | ||
| 1150 | if (yes.value() == no.value()) { | ||
| 1151 | s.inst_map.add(s.frame.currentIndex(), yes); | ||
| 1152 | s.frame.advance(); | ||
| 1153 | return .@"continue"; | ||
| 1154 | } | ||
| 1155 | |||
| 1143 | const union_type = s.peerTypeResolution(&.{ s.typeOfRef(yes), s.typeOfRef(no) }); | 1156 | const union_type = s.peerTypeResolution(&.{ s.typeOfRef(yes), s.typeOfRef(no) }); |
| 1144 | 1157 | ||
| 1145 | const yes_coerced = try s.coerceBranch(s.lazyNode(.lhs), yes, union_type); | 1158 | const yes_coerced = try s.coerceBranch(s.lazyNode(.lhs), yes, union_type); |
| ... | @@ -1166,11 +1179,15 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1166,11 +1179,15 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1166 | 1179 | ||
| 1167 | const return_type = switch (data.func) { | 1180 | const return_type = switch (data.func) { |
| 1168 | .argument => |arg_num| ret: { | 1181 | .argument => |arg_num| ret: { |
| 1182 | s.inst_map.add( | ||
| 1183 | func_data.params.slice(&s.hir)[arg_num].unwrap().?.decode().index, | ||
| 1184 | operand, | ||
| 1185 | ); | ||
| 1186 | |||
| 1169 | for ( | 1187 | for ( |
| 1170 | func_data.params.slice(&s.hir)[arg_num + 1 ..], | 1188 | func_data.params.slice(&s.hir)[arg_num + 1 ..], |
| 1171 | reserve.items(.params)[arg_num + 1 ..], | ||
| 1172 | arg_num + 1.., | 1189 | arg_num + 1.., |
| 1173 | ) |param, *dst, i| { | 1190 | ) |param, i| { |
| 1174 | const known = param.unwrap() orelse @panic("TODO: inferred param sets"); | 1191 | const known = param.unwrap() orelse @panic("TODO: inferred param sets"); |
| 1175 | switch (known.decode()) { | 1192 | switch (known.decode()) { |
| 1176 | .value => |val| { | 1193 | .value => |val| { |
| ... | @@ -1181,7 +1198,6 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1181,7 +1198,6 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1181 | .type = ty, | 1198 | .type = ty, |
| 1182 | }); | 1199 | }); |
| 1183 | } | 1200 | } |
| 1184 | dst.* = ty; | ||
| 1185 | }, | 1201 | }, |
| 1186 | .index => |block| { | 1202 | .index => |block| { |
| 1187 | const mode = s.frame.mode; | 1203 | const mode = s.frame.mode; |
| ... | @@ -1225,6 +1241,13 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1225,6 +1241,13 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1225 | }, | 1241 | }, |
| 1226 | }; | 1242 | }; |
| 1227 | 1243 | ||
| 1244 | for (func_data.params.slice(&s.hir), reserve.items(.params)) |param, *dst| { | ||
| 1245 | dst.* = (switch (param.unwrap().?.decode()) { | ||
| 1246 | .value => |value| value, | ||
| 1247 | .index => |index| s.inst_map.get(index).decode().value, | ||
| 1248 | }).toTypeUnsafe(s.ip); | ||
| 1249 | } | ||
| 1250 | |||
| 1228 | try s.postVisitFunctionPrototype(reserve, return_type); | 1251 | try s.postVisitFunctionPrototype(reserve, return_type); |
| 1229 | return .@"continue"; | 1252 | return .@"continue"; |
| 1230 | }, | 1253 | }, |
| ... | @@ -1319,6 +1342,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1319,6 +1342,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1319 | const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yn[0], union_type); | 1342 | const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yn[0], union_type); |
| 1320 | const no_coerced = try s.coerceValue(s.lazyNode(.rhs), yn[1], union_type); | 1343 | const no_coerced = try s.coerceValue(s.lazyNode(.rhs), yn[1], union_type); |
| 1321 | 1344 | ||
| 1345 | if (yes_coerced == no_coerced) { | ||
| 1346 | s.inst_map.add(s.frame.currentIndex(), yes_coerced.toAir()); | ||
| 1347 | s.frame.advance(); | ||
| 1348 | return; | ||
| 1349 | } | ||
| 1350 | |||
| 1322 | s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{ | 1351 | s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{ |
| 1323 | .cond = condition, | 1352 | .cond = condition, |
| 1324 | .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{ | 1353 | .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{ |
| ... | @@ -1573,39 +1602,54 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) | ... | @@ -1573,39 +1602,54 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1573 | } | 1602 | } |
| 1574 | } | 1603 | } |
| 1575 | 1604 | ||
| 1576 | fn emitRuntimeBlock( | 1605 | fn emitRuntimeBranchForIf( |
| 1577 | s: *Sema, | 1606 | s: *Sema, |
| 1578 | from: Hir.Inst.Index, | 1607 | from: Hir.Inst.Index, |
| 1579 | break_operand: Air.Inst.Ref, | 1608 | result_value: Air.Inst.Ref, |
| 1580 | mark: core.U32Stack.Mark, | 1609 | mark: core.U32Stack.Mark, |
| 1581 | ) !void { | 1610 | ) !void { |
| 1582 | s.inst_map.add( | 1611 | s.inst_map.add( |
| 1583 | from, | 1612 | from, |
| 1584 | if (mark.len() == 0) brk: { | 1613 | if (mark.len() == 0 and result_value.kind == .value) brk: { |
| 1585 | assert(break_operand.kind == .value); | 1614 | break :brk result_value; |
| 1586 | break :brk break_operand; | 1615 | } else (try s.createRuntimeBlock(mark, result_value)).toRef(), |
| 1587 | } else brk: { | ||
| 1588 | assert(s.frame.mode == .run_time); | ||
| 1589 | const func = &s.func.?; | ||
| 1590 | const idx = func.extra.list.items.len; | ||
| 1591 | const block_instructions = mark.slice(u32); | ||
| 1592 | try func.extra.list.ensureUnusedCapacity(s.gpa, block_instructions.len); | ||
| 1593 | const block = try s.addIndexUntracked(.block, .{ | ||
| 1594 | .start = @intCast(idx), | ||
| 1595 | .len = @intCast(block_instructions.len + 1), | ||
| 1596 | }); | ||
| 1597 | const brk_instruction = try s.addIndexUntracked(.@"break", .{ | ||
| 1598 | .block = block, | ||
| 1599 | .value = break_operand, | ||
| 1600 | }); | ||
| 1601 | func.extra.list.appendSliceAssumeCapacity(block_instructions); | ||
| 1602 | func.extra.list.appendAssumeCapacity(@intFromEnum(brk_instruction)); | ||
| 1603 | break :brk block.toRef(); | ||
| 1604 | }, | ||
| 1605 | ); | 1616 | ); |
| 1606 | mark.deinit(); | 1617 | mark.deinit(); |
| 1607 | } | 1618 | } |
| 1608 | 1619 | ||
| 1620 | fn createRuntimeBlock(s: *Sema, mark: core.U32Stack.Mark, result: ?Air.Inst.Ref) !Air.Inst.Index { | ||
| 1621 | assert(mark.len() > 0); | ||
| 1622 | assert(s.frame.mode == .run_time); | ||
| 1623 | |||
| 1624 | const func = &s.func.?; | ||
| 1625 | const idx = func.extra.list.items.len; | ||
| 1626 | |||
| 1627 | if (core.runtime_safety) { | ||
| 1628 | const ty = s.tempAir().typeOfIndex(mark.slice(Air.Inst.Index)[0], s.ip); | ||
| 1629 | if (result != null) { | ||
| 1630 | assert(ty != .never); // control flow already ended | ||
| 1631 | } else { | ||
| 1632 | assert(ty == .never); // does not stop control flow | ||
| 1633 | } | ||
| 1634 | } | ||
| 1635 | |||
| 1636 | const block_instructions = mark.slice(u32); | ||
| 1637 | try func.extra.list.ensureUnusedCapacity(s.gpa, block_instructions.len); | ||
| 1638 | const block = try s.addIndexUntracked(.block, .{ | ||
| 1639 | .start = @intCast(idx), | ||
| 1640 | .len = @intCast(block_instructions.len + @intFromBool(result != null)), | ||
| 1641 | }); | ||
| 1642 | func.extra.list.appendSliceAssumeCapacity(block_instructions); | ||
| 1643 | if (result) |value| { | ||
| 1644 | const brk_instruction = try s.addIndexUntracked(.@"break", .{ | ||
| 1645 | .block = block, | ||
| 1646 | .value = value, | ||
| 1647 | }); | ||
| 1648 | func.extra.list.appendAssumeCapacity(@intFromEnum(brk_instruction)); | ||
| 1649 | } | ||
| 1650 | return block; | ||
| 1651 | } | ||
| 1652 | |||
| 1609 | const BinaryOp = enum { | 1653 | const BinaryOp = enum { |
| 1610 | add, | 1654 | add, |
| 1611 | sub, | 1655 | sub, |
| ... | @@ -1889,7 +1933,8 @@ fn coerceBranch(s: *Sema, src: LazyNode, ref: Air.Inst.Ref, to: Type) !Air.Inst. | ... | @@ -1889,7 +1933,8 @@ fn coerceBranch(s: *Sema, src: LazyNode, ref: Air.Inst.Ref, to: Type) !Air.Inst. |
| 1889 | return switch (ref.decode()) { | 1933 | return switch (ref.decode()) { |
| 1890 | .index => |block| { | 1934 | .index => |block| { |
| 1891 | if (s.typeOfIndex(block) != to) return ref; | 1935 | if (s.typeOfIndex(block) != to) return ref; |
| 1892 | @panic("TODO"); | 1936 | // @panic("TODO"); |
| 1937 | return ref; | ||
| 1893 | }, | 1938 | }, |
| 1894 | .value => |value| (try s.coerceValue(src, value, to)).toAir(), | 1939 | .value => |value| (try s.coerceValue(src, value, to)).toAir(), |
| 1895 | }; | 1940 | }; |
| ... | @@ -2225,6 +2270,38 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Hir.In | ... | @@ -2225,6 +2270,38 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Hir.In |
| 2225 | 2270 | ||
| 2226 | try s.ensureAtRuntime(s.lazyNode(.main_node)); | 2271 | try s.ensureAtRuntime(s.lazyNode(.main_node)); |
| 2227 | 2272 | ||
| 2273 | if (s.generate_safety_checks) { | ||
| 2274 | const is_above_bounds = try s.addRefMark(.ge, .{ | ||
| 2275 | .lhs = index, | ||
| 2276 | .rhs = try s.addRefMark(.array_len, .{ | ||
| 2277 | .operand = lhs, | ||
| 2278 | }), | ||
| 2279 | }); | ||
| 2280 | const is_oob = switch (index.decode()) { | ||
| 2281 | .value => |value| is_oob: { | ||
| 2282 | const int = value.toInt32(s.ip) orelse unreachable; | ||
| 2283 | if (int < 0) @panic("TODO: always out of bounds"); | ||
| 2284 | break :is_oob is_above_bounds; | ||
| 2285 | }, | ||
| 2286 | .index => is_oob: { | ||
| 2287 | const is_below_zero = brk: { | ||
| 2288 | const mark = s.scratch.mark(); | ||
| 2289 | defer mark.deinit(); | ||
| 2290 | |||
| 2291 | const is_below_zero = try s.addRefMark(.lt, .{ | ||
| 2292 | .lhs = index, | ||
| 2293 | .rhs = try s.makeValueAir(.{ .int32 = 0 }), | ||
| 2294 | }); | ||
| 2295 | |||
| 2296 | break :brk try s.createRuntimeBlock(mark, is_below_zero); | ||
| 2297 | }; | ||
| 2298 | break :is_oob try s.emitOrFromRefAndBlock(is_above_bounds, is_below_zero); | ||
| 2299 | }, | ||
| 2300 | }; | ||
| 2301 | |||
| 2302 | try s.emitSafetyCheck(is_oob, "array out of bounds"); | ||
| 2303 | } | ||
| 2304 | |||
| 2228 | return s.addRef(.field_array, .{ | 2305 | return s.addRef(.field_array, .{ |
| 2229 | .lhs = lhs, | 2306 | .lhs = lhs, |
| 2230 | .rhs = index, | 2307 | .rhs = index, |
| ... | @@ -2233,6 +2310,17 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Hir.In | ... | @@ -2233,6 +2310,17 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Hir.In |
| 2233 | } | 2310 | } |
| 2234 | } | 2311 | } |
| 2235 | 2312 | ||
| 2313 | fn emitOrFromRefAndBlock(s: *Sema, lhs: Air.Inst.Ref, short_circuiting_rhs: Air.Inst.Index) !Air.Inst.Ref { | ||
| 2314 | return try s.addRefMark(.@"if", .{ | ||
| 2315 | .cond = lhs, | ||
| 2316 | .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{ | ||
| 2317 | .yes = Value.bool_true.toAir(), | ||
| 2318 | .no = short_circuiting_rhs.toRef(), | ||
| 2319 | .union_ty = .bool, | ||
| 2320 | }), | ||
| 2321 | }); | ||
| 2322 | } | ||
| 2323 | |||
| 2236 | fn fieldAccessTuple( | 2324 | fn fieldAccessTuple( |
| 2237 | s: *Sema, | 2325 | s: *Sema, |
| 2238 | lhs: Air.Inst.Ref, | 2326 | lhs: Air.Inst.Ref, |
| ... | @@ -2297,6 +2385,27 @@ fn postVisitFunctionPrototype(s: *Sema, reserve: FunctionReserve, return_type: ? | ... | @@ -2297,6 +2385,27 @@ fn postVisitFunctionPrototype(s: *Sema, reserve: FunctionReserve, return_type: ? |
| 2297 | try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } }); | 2385 | try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } }); |
| 2298 | } | 2386 | } |
| 2299 | 2387 | ||
| 2388 | fn emitSafetyCheck(s: *Sema, should_panic: Air.Inst.Ref, panic_message: []const u8) !void { | ||
| 2389 | const yes = brk: { | ||
| 2390 | const mark = s.scratch.mark(); | ||
| 2391 | defer mark.deinit(); | ||
| 2392 | |||
| 2393 | _ = try s.addRefMark(.panic, .{ | ||
| 2394 | .operand = try s.makeValueAir(.{ .string = panic_message }), | ||
| 2395 | }); | ||
| 2396 | |||
| 2397 | break :brk try s.createRuntimeBlock(mark, null); | ||
| 2398 | }; | ||
| 2399 | _ = try s.addRefMark(.@"if", .{ | ||
| 2400 | .cond = should_panic, | ||
| 2401 | .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{ | ||
| 2402 | .yes = yes.toRef(), | ||
| 2403 | .no = .void, | ||
| 2404 | .union_ty = .void, | ||
| 2405 | }), | ||
| 2406 | }); | ||
| 2407 | } | ||
| 2408 | |||
| 2300 | // | 2409 | // |
| 2301 | 2410 | ||
| 2302 | fn tempAir(s: *const Sema) Air { | 2411 | fn tempAir(s: *const Sema) Air { |
| ... | @@ -2342,6 +2451,12 @@ fn addRef(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref | ... | @@ -2342,6 +2451,12 @@ fn addRef(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref |
| 2342 | return (try s.addIndex(tag, data)).toRef(); | 2451 | return (try s.addIndex(tag, data)).toRef(); |
| 2343 | } | 2452 | } |
| 2344 | 2453 | ||
| 2454 | fn addRefMark(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref { | ||
| 2455 | const inst = try s.addIndex(tag, data); | ||
| 2456 | s.markInst(inst); | ||
| 2457 | return inst.toRef(); | ||
| 2458 | } | ||
| 2459 | |||
| 2345 | fn typeOfIndex(s: *Sema, i: Air.Inst.Index) Type { | 2460 | fn typeOfIndex(s: *Sema, i: Air.Inst.Index) Type { |
| 2346 | return s.tempAir().typeOfIndex(i, s.ip); | 2461 | return s.tempAir().typeOfIndex(i, s.ip); |
| 2347 | } | 2462 | } |
src/core/Tokenizer.zig+2| ... | @@ -55,6 +55,7 @@ pub const Token = struct { | ... | @@ -55,6 +55,7 @@ pub const Token = struct { |
| 55 | module_doc_comment, | 55 | module_doc_comment, |
| 56 | 56 | ||
| 57 | // sort-lines: start | 57 | // sort-lines: start |
| 58 | kw_and, | ||
| 58 | kw_assert, | 59 | kw_assert, |
| 59 | kw_break, | 60 | kw_break, |
| 60 | kw_comptime, | 61 | kw_comptime, |
| ... | @@ -70,6 +71,7 @@ pub const Token = struct { | ... | @@ -70,6 +71,7 @@ pub const Token = struct { |
| 70 | kw_let, | 71 | kw_let, |
| 71 | kw_match, | 72 | kw_match, |
| 72 | kw_module, | 73 | kw_module, |
| 74 | kw_or, | ||
| 73 | kw_pub, | 75 | kw_pub, |
| 74 | kw_pure, | 76 | kw_pure, |
| 75 | kw_return, | 77 | kw_return, |
src/core/Transform.zig+87-29| ... | @@ -363,19 +363,24 @@ const Symbol = struct { | ... | @@ -363,19 +363,24 @@ const Symbol = struct { |
| 363 | } | 363 | } |
| 364 | }; | 364 | }; |
| 365 | 365 | ||
| 366 | const built_in_ids = std.StaticStringMap(Inst.Ref).initComptime(.{ | 366 | const BuiltIn = struct { |
| 367 | .{ "void", .void }, | 367 | type: Inst.Ref, |
| 368 | .{ "true", .bool_true }, | 368 | value: Inst.Ref, |
| 369 | .{ "false", .bool_false }, | 369 | }; |
| 370 | .{ "int", .int32_type }, | 370 | |
| 371 | .{ "float", .float_type }, | 371 | const built_in_ids = std.StaticStringMap(BuiltIn).initComptime(.{ |
| 372 | .{ "string", .string_type }, | 372 | .{ "void", .{ .value = .void, .type = .void } }, |
| 373 | .{ "opaque", .opaque_type }, | 373 | .{ "true", .{ .value = .bool_true, .type = .bool_type } }, |
| 374 | // .{ "bigint", .bigint_type }, | 374 | .{ "false", .{ .value = .bool_false, .type = .bool_type } }, |
| 375 | .{ "bool", .bool_type }, | 375 | .{ "int", .{ .value = .int32_type, .type = .type_type } }, |
| 376 | .{ "never", .never_type }, | 376 | .{ "float", .{ .value = .float_type, .type = .type_type } }, |
| 377 | .{ "type", .type_type }, | 377 | .{ "string", .{ .value = .string_type, .type = .type_type } }, |
| 378 | // .{ "std", .standard_library }, | 378 | .{ "opaque", .{ .value = .opaque_type, .type = .type_type } }, |
| 379 | // .{ "bigint", .{ .value = .bigint_type, .type = .type_type } }, | ||
| 380 | .{ "bool", .{ .value = .bool_type, .type = .type_type } }, | ||
| 381 | .{ "never", .{ .value = .never_type, .type = .type_type } }, | ||
| 382 | .{ "type", .{ .value = .type_type, .type = .type_type } }, | ||
| 383 | // .{ "std", .{ .value = .standard_library, .type = ... } }, | ||
| 379 | }); | 384 | }); |
| 380 | 385 | ||
| 381 | fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.Index { | 386 | fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.Index { |
| ... | @@ -940,8 +945,13 @@ fn transformExpression( | ... | @@ -940,8 +945,13 @@ fn transformExpression( |
| 940 | return .void; | 945 | return .void; |
| 941 | } | 946 | } |
| 942 | 947 | ||
| 943 | if (built_in_ids.get(id)) |ref| { | 948 | if (built_in_ids.get(id)) |built_in| { |
| 944 | return ref; | 949 | // Elide the coercion when the type is known to be the type of the built-in |
| 950 | if (result.* == .ref and result.ref == built_in.type) { | ||
| 951 | result.* = .used; | ||
| 952 | } | ||
| 953 | |||
| 954 | return built_in.value; | ||
| 945 | } | 955 | } |
| 946 | 956 | ||
| 947 | const lookup = t.lookupId(id) orelse { | 957 | const lookup = t.lookupId(id) orelse { |
| ... | @@ -1048,8 +1058,6 @@ fn transformExpression( | ... | @@ -1048,8 +1058,6 @@ fn transformExpression( |
| 1048 | .div, | 1058 | .div, |
| 1049 | .rem, | 1059 | .rem, |
| 1050 | .pow, | 1060 | .pow, |
| 1051 | .@"and", | ||
| 1052 | .@"or", | ||
| 1053 | .bit_and, | 1061 | .bit_and, |
| 1054 | .bit_or, | 1062 | .bit_or, |
| 1055 | .bit_xor, | 1063 | .bit_xor, |
| ... | @@ -1262,7 +1270,7 @@ fn transformExpression( | ... | @@ -1262,7 +1270,7 @@ fn transformExpression( |
| 1262 | // .fetch => Builtin.fetch(t, decl, node, args), | 1270 | // .fetch => Builtin.fetch(t, decl, node, args), |
| 1263 | .compile_error => Builtin.builtin_compileError(t, ctx, node, args), | 1271 | .compile_error => Builtin.builtin_compileError(t, ctx, node, args), |
| 1264 | .type_of => Builtin.builtin_typeOf(t, ctx, node, args), | 1272 | .type_of => Builtin.builtin_typeOf(t, ctx, node, args), |
| 1265 | else => @panic("TODO"), | 1273 | else => |tag| std.debug.panic("TODO builtin {s}", .{@tagName(tag)}), |
| 1266 | }; | 1274 | }; |
| 1267 | }, | 1275 | }, |
| 1268 | 1276 | ||
| ... | @@ -1447,17 +1455,7 @@ fn transformExpression( | ... | @@ -1447,17 +1455,7 @@ fn transformExpression( |
| 1447 | else | 1455 | else |
| 1448 | .void; | 1456 | .void; |
| 1449 | 1457 | ||
| 1450 | const coerced_operand = switch (break_result) { | 1458 | const coerced_operand = try t.maybeCoerce(ctx.nodeOffset(node), operand, &break_result); |
| 1451 | .none, .used => operand, | ||
| 1452 | else => try t.addRef(.coercion, .{ | ||
| 1453 | .src_node = ctx.nodeOffset(node), | ||
| 1454 | .extra_index = try t.extra.append(t.gpa, Hir.Coercion, .{ | ||
| 1455 | .type = try break_result.consume(t) orelse | ||
| 1456 | unreachable, // `.none` already handled | ||
| 1457 | .operand = operand, | ||
| 1458 | }), | ||
| 1459 | }), | ||
| 1460 | }; | ||
| 1461 | 1459 | ||
| 1462 | _ = try t.addIndex(.@"break", .{ | 1460 | _ = try t.addIndex(.@"break", .{ |
| 1463 | .src_node = ctx.nodeOffset(node), | 1461 | .src_node = ctx.nodeOffset(node), |
| ... | @@ -1697,6 +1695,52 @@ fn transformExpression( | ... | @@ -1697,6 +1695,52 @@ fn transformExpression( |
| 1697 | }); | 1695 | }); |
| 1698 | }, | 1696 | }, |
| 1699 | 1697 | ||
| 1698 | .bool_and => { | ||
| 1699 | const bin = t.ast.nodeData(node).bin; | ||
| 1700 | |||
| 1701 | var lhs_result: ResultType = .{ .ref = .bool_type }; | ||
| 1702 | const lhs = try t.maybeCoerce( | ||
| 1703 | ctx.nodeOffset(node), | ||
| 1704 | try t.transformExpression(ctx, &lhs_result, bin.lhs) orelse | ||
| 1705 | return null, | ||
| 1706 | &lhs_result, | ||
| 1707 | ); | ||
| 1708 | |||
| 1709 | const rhs = try t.transformToBlock(ctx, bin.rhs, .untracked_result, .{ .ref = .bool_type }, .run_time); | ||
| 1710 | |||
| 1711 | return try t.addRef(.@"if", .{ | ||
| 1712 | .src_node = ctx.nodeOffset(node), | ||
| 1713 | .extra_index = try t.extra.append(t.gpa, Hir.If, .{ | ||
| 1714 | .cond = lhs, | ||
| 1715 | .yes = rhs, | ||
| 1716 | .no = .bool_false, | ||
| 1717 | }), | ||
| 1718 | }); | ||
| 1719 | }, | ||
| 1720 | |||
| 1721 | .bool_or => { | ||
| 1722 | const bin = t.ast.nodeData(node).bin; | ||
| 1723 | |||
| 1724 | var lhs_result: ResultType = .{ .ref = .bool_type }; | ||
| 1725 | const lhs = try t.maybeCoerce( | ||
| 1726 | ctx.nodeOffset(node), | ||
| 1727 | try t.transformExpression(ctx, &lhs_result, bin.lhs) orelse | ||
| 1728 | return null, | ||
| 1729 | &lhs_result, | ||
| 1730 | ); | ||
| 1731 | |||
| 1732 | const rhs = try t.transformToBlock(ctx, bin.rhs, .untracked_result, .{ .ref = .bool_type }, .run_time); | ||
| 1733 | |||
| 1734 | return try t.addRef(.@"if", .{ | ||
| 1735 | .src_node = ctx.nodeOffset(node), | ||
| 1736 | .extra_index = try t.extra.append(t.gpa, Hir.If, .{ | ||
| 1737 | .cond = lhs, | ||
| 1738 | .yes = .bool_true, | ||
| 1739 | .no = rhs, | ||
| 1740 | }), | ||
| 1741 | }); | ||
| 1742 | }, | ||
| 1743 | |||
| 1700 | else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}), | 1744 | else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}), |
| 1701 | } | 1745 | } |
| 1702 | } | 1746 | } |
| ... | @@ -2198,6 +2242,20 @@ fn getInstData(t: *Transform, index: Inst.Index) *EncodedData { | ... | @@ -2198,6 +2242,20 @@ fn getInstData(t: *Transform, index: Inst.Index) *EncodedData { |
| 2198 | return &t.insts.items(.data)[@intFromEnum(index)]; | 2242 | return &t.insts.items(.data)[@intFromEnum(index)]; |
| 2199 | } | 2243 | } |
| 2200 | 2244 | ||
| 2245 | fn maybeCoerce(t: *Transform, node_offset: i32, operand: Inst.Ref, result: *ResultType) !Inst.Ref { | ||
| 2246 | return switch (result.*) { | ||
| 2247 | .none, .used => operand, | ||
| 2248 | else => try t.addRef(.coercion, .{ | ||
| 2249 | .src_node = node_offset, | ||
| 2250 | .extra_index = try t.extra.append(t.gpa, Hir.Coercion, .{ | ||
| 2251 | .type = try result.consume(t) orelse | ||
| 2252 | unreachable, // `.none` already handled | ||
| 2253 | .operand = operand, | ||
| 2254 | }), | ||
| 2255 | }), | ||
| 2256 | }; | ||
| 2257 | } | ||
| 2258 | |||
| 2201 | fn scratchToRefList(t: *Transform, scratch_top: core.U32Stack.Mark) !RefList { | 2259 | fn scratchToRefList(t: *Transform, scratch_top: core.U32Stack.Mark) !RefList { |
| 2202 | const items = scratch_top.slice(u32); | 2260 | const items = scratch_top.slice(u32); |
| 2203 | const extra_top = t.extra.list.items.len; | 2261 | const extra_top = t.extra.list.items.len; |
src/core/js/Link.zig+31-3| ... | @@ -388,6 +388,12 @@ fn printExprLevel( | ... | @@ -388,6 +388,12 @@ fn printExprLevel( |
| 388 | } | 388 | } |
| 389 | }, | 389 | }, |
| 390 | 390 | ||
| 391 | .stmt_throw => { | ||
| 392 | const stmt: Tree.StmtThrow = tree.nodeData(.stmt_throw, node); | ||
| 393 | try writer.writeAll("throw "); | ||
| 394 | try link.printExpr(tree, stmt.operand, writer); | ||
| 395 | }, | ||
| 396 | |||
| 391 | .stmt_declare_local => { | 397 | .stmt_declare_local => { |
| 392 | const stmt: Tree.StmtDeclareLocal = tree.nodeData(.stmt_declare_local, node); | 398 | const stmt: Tree.StmtDeclareLocal = tree.nodeData(.stmt_declare_local, node); |
| 393 | try writer.writeAll("let "); | 399 | try writer.writeAll("let "); |
| ... | @@ -435,10 +441,14 @@ fn printExprLevel( | ... | @@ -435,10 +441,14 @@ fn printExprLevel( |
| 435 | .eq, | 441 | .eq, |
| 436 | .lt, | 442 | .lt, |
| 437 | .gt, | 443 | .gt, |
| 444 | .ge, | ||
| 445 | .le, | ||
| 438 | .assign_add, | 446 | .assign_add, |
| 439 | .assign_sub, | 447 | .assign_sub, |
| 440 | .assign_mul, | 448 | .assign_mul, |
| 441 | .assign_div, | 449 | .assign_div, |
| 450 | .@"and", | ||
| 451 | .@"or", | ||
| 442 | => |tag| { | 452 | => |tag| { |
| 443 | const bin: Tree.Bin = tree.nodeData(tag, node); | 453 | const bin: Tree.Bin = tree.nodeData(tag, node); |
| 444 | const op: Operator = @field(Operator, @tagName(tag)); | 454 | const op: Operator = @field(Operator, @tagName(tag)); |
| ... | @@ -466,6 +476,14 @@ fn printExprLevel( | ... | @@ -466,6 +476,14 @@ fn printExprLevel( |
| 466 | try writer.writeAll(")"); | 476 | try writer.writeAll(")"); |
| 467 | }, | 477 | }, |
| 468 | 478 | ||
| 479 | .comma_chain => { | ||
| 480 | const bin: Tree.IndexList = tree.nodeData(.comma_chain, node); | ||
| 481 | for (bin.slice(tree), 0..) |expr, i| { | ||
| 482 | if (i != 0) try writer.writeAll(", "); | ||
| 483 | try link.printExprLevel(tree, expr, .assignment, writer); | ||
| 484 | } | ||
| 485 | }, | ||
| 486 | |||
| 469 | inline .negate, | 487 | inline .negate, |
| 470 | .bool_not, | 488 | .bool_not, |
| 471 | .pre_dec, | 489 | .pre_dec, |
| ... | @@ -483,7 +501,7 @@ fn printExprLevel( | ... | @@ -483,7 +501,7 @@ fn printExprLevel( |
| 483 | const wrap = Level.conditional.needsParens(level); | 501 | const wrap = Level.conditional.needsParens(level); |
| 484 | if (wrap) | 502 | if (wrap) |
| 485 | try writer.writeAll("("); | 503 | try writer.writeAll("("); |
| 486 | try link.printExpr(tree, ternary.cond, writer); | 504 | try link.printExprLevel(tree, ternary.cond, .logical_or, writer); |
| 487 | try writer.writeAll(" ? "); | 505 | try writer.writeAll(" ? "); |
| 488 | try link.printExpr(tree, data.yes, writer); | 506 | try link.printExpr(tree, data.yes, writer); |
| 489 | try writer.writeAll(" : "); | 507 | try writer.writeAll(" : "); |
| ... | @@ -788,10 +806,14 @@ const Operator = enum { | ... | @@ -788,10 +806,14 @@ const Operator = enum { |
| 788 | eq, | 806 | eq, |
| 789 | lt, | 807 | lt, |
| 790 | gt, | 808 | gt, |
| 809 | ge, | ||
| 810 | le, | ||
| 791 | negate, | 811 | negate, |
| 792 | bool_not, | 812 | bool_not, |
| 793 | pre_inc, | 813 | pre_inc, |
| 794 | pre_dec, | 814 | pre_dec, |
| 815 | @"and", | ||
| 816 | @"or", | ||
| 795 | 817 | ||
| 796 | pub fn level(op: Operator) Level { | 818 | pub fn level(op: Operator) Level { |
| 797 | return switch (op) { | 819 | return switch (op) { |
| ... | @@ -804,8 +826,9 @@ const Operator = enum { | ... | @@ -804,8 +826,9 @@ const Operator = enum { |
| 804 | .add, .sub => .additive, | 826 | .add, .sub => .additive, |
| 805 | .mul, .div => .multiplicative, | 827 | .mul, .div => .multiplicative, |
| 806 | .eq => .equality, | 828 | .eq => .equality, |
| 807 | .lt, .gt => .relational, | 829 | .lt, .gt, .ge, .le => .relational, |
| 808 | 830 | .@"and" => .logical_and, | |
| 831 | .@"or" => .logical_or, | ||
| 809 | .pre_inc, | 832 | .pre_inc, |
| 810 | .pre_dec, | 833 | .pre_dec, |
| 811 | .bool_not, | 834 | .bool_not, |
| ... | @@ -828,6 +851,11 @@ const Operator = enum { | ... | @@ -828,6 +851,11 @@ const Operator = enum { |
| 828 | .eq => "===", | 851 | .eq => "===", |
| 829 | .lt => "<", | 852 | .lt => "<", |
| 830 | .gt => ">", | 853 | .gt => ">", |
| 854 | .le => "<=", | ||
| 855 | .ge => ">=", | ||
| 856 | |||
| 857 | .@"and" => "&&", | ||
| 858 | .@"or" => "||", | ||
| 831 | 859 | ||
| 832 | .pre_inc => "++", | 860 | .pre_inc => "++", |
| 833 | .pre_dec => "++", | 861 | .pre_dec => "++", |
src/core/js/NodeGen.zig+117-10| ... | @@ -157,6 +157,8 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { | ... | @@ -157,6 +157,8 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 157 | inline .gt, | 157 | inline .gt, |
| 158 | .lt, | 158 | .lt, |
| 159 | .eq, | 159 | .eq, |
| 160 | .ge, | ||
| 161 | .le, | ||
| 160 | .add, | 162 | .add, |
| 161 | .sub, | 163 | .sub, |
| 162 | .mul, | 164 | .mul, |
| ... | @@ -250,6 +252,28 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { | ... | @@ -250,6 +252,28 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 250 | try c.markExpression(inst, node); | 252 | try c.markExpression(inst, node); |
| 251 | }, | 253 | }, |
| 252 | 254 | ||
| 255 | .panic => { | ||
| 256 | const data: Air.Un = c.air.instData(.panic, inst); | ||
| 257 | const string = try c.fetchExpression(data.operand); | ||
| 258 | |||
| 259 | const error_constructor_string = (try c.ip.getOrPutString(c.gpa, "Error", .maybe_embedded_nulls)).withLen("Error".len); | ||
| 260 | try c.extern_symbols.put(c.gpa, error_constructor_string, {}); | ||
| 261 | |||
| 262 | try c.extra.list.ensureUnusedCapacity(c.gpa, 1); | ||
| 263 | const start = c.extra.list.items.len; | ||
| 264 | c.extra.list.appendAssumeCapacity(@intFromEnum(string)); | ||
| 265 | |||
| 266 | try c.addStmt(try c.add(.stmt_throw, .{ | ||
| 267 | .operand = try c.add(.js_new, .{ | ||
| 268 | .base = try c.add(.global, .{ .name = error_constructor_string }), | ||
| 269 | .args = try c.extra.append(c.gpa, Tree.IndexList, .{ | ||
| 270 | .start = @intCast(start), | ||
| 271 | .len = 1, | ||
| 272 | }), | ||
| 273 | }), | ||
| 274 | })); | ||
| 275 | }, | ||
| 276 | |||
| 253 | .field_opaque => { | 277 | .field_opaque => { |
| 254 | const data: Air.Bin = c.air.instData(.field_opaque, inst); | 278 | const data: Air.Bin = c.air.instData(.field_opaque, inst); |
| 255 | if (data.rhs.value()) |field_value| { | 279 | if (data.rhs.value()) |field_value| { |
| ... | @@ -336,8 +360,34 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { | ... | @@ -336,8 +360,34 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 336 | if (c.canLowerToExpression(extra.yes) and | 360 | if (c.canLowerToExpression(extra.yes) and |
| 337 | c.canLowerToExpression(extra.no)) | 361 | c.canLowerToExpression(extra.no)) |
| 338 | { | 362 | { |
| 363 | if (extra.no.value()) |no_value| { | ||
| 364 | if (no_value == .bool_false) { | ||
| 365 | const yes = try c.lowerToExpression(extra.yes); | ||
| 366 | |||
| 367 | const conditional = try c.add(.@"and", .{ | ||
| 368 | .lhs = cond, | ||
| 369 | .rhs = yes, | ||
| 370 | }); | ||
| 371 | try c.markExpression(inst, conditional); | ||
| 372 | return; | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | if (extra.yes.value()) |yes_value| { | ||
| 377 | if (yes_value == .bool_true) { | ||
| 378 | const no = try c.lowerToExpression(extra.no); | ||
| 379 | |||
| 380 | const conditional = try c.add(.@"or", .{ | ||
| 381 | .lhs = cond, | ||
| 382 | .rhs = no, | ||
| 383 | }); | ||
| 384 | try c.markExpression(inst, conditional); | ||
| 385 | return; | ||
| 386 | } | ||
| 387 | } | ||
| 388 | |||
| 339 | const yes = try c.lowerToExpression(extra.yes); | 389 | const yes = try c.lowerToExpression(extra.yes); |
| 340 | const no = try c.lowerToExpression(extra.yes); | 390 | const no = try c.lowerToExpression(extra.no); |
| 341 | 391 | ||
| 342 | const ternary = try c.add(.ternary, .{ | 392 | const ternary = try c.add(.ternary, .{ |
| 343 | .cond = cond, | 393 | .cond = cond, |
| ... | @@ -559,7 +609,7 @@ fn fetchInstExpression(c: *NodeGen, index: Air.Inst.Index) !Node.Index { | ... | @@ -559,7 +609,7 @@ fn fetchInstExpression(c: *NodeGen, index: Air.Inst.Index) !Node.Index { |
| 559 | switch (flags.uses) { | 609 | switch (flags.uses) { |
| 560 | .none => { | 610 | .none => { |
| 561 | // incorrectly marked instruction as unused | 611 | // incorrectly marked instruction as unused |
| 562 | std.debug.panic("called fetchInstExpression(#{d}), but no expression was generated", .{ | 612 | std.debug.panic("called fetchInstExpression(#{d}), but it was marked as unused", .{ |
| 563 | @intFromEnum(index), | 613 | @intFromEnum(index), |
| 564 | }); | 614 | }); |
| 565 | }, | 615 | }, |
| ... | @@ -722,7 +772,6 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void { | ... | @@ -722,7 +772,6 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void { |
| 722 | const next_label = c.next_label; | 772 | const next_label = c.next_label; |
| 723 | 773 | ||
| 724 | try c.scratch.appendSlice(c.arena, &.{ | 774 | try c.scratch.appendSlice(c.arena, &.{ |
| 725 | // @intFromEnum(if (temporary) |temp| temp.toOptional() else .none), | ||
| 726 | @intFromEnum(Node.Index.Optional.none), | 775 | @intFromEnum(Node.Index.Optional.none), |
| 727 | next_label, | 776 | next_label, |
| 728 | }); | 777 | }); |
| ... | @@ -749,6 +798,46 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void { | ... | @@ -749,6 +798,46 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void { |
| 749 | })); | 798 | })); |
| 750 | } | 799 | } |
| 751 | 800 | ||
| 801 | fn codeGenBlockAsExpression(c: *NodeGen, inst: Air.Inst.Index) !Node.Index { | ||
| 802 | const t = tracer.trace(@src()); | ||
| 803 | defer t.end(); | ||
| 804 | |||
| 805 | const scratch_top = c.scratch.items.len; | ||
| 806 | errdefer c.scratch.shrinkRetainingCapacity(scratch_top); | ||
| 807 | |||
| 808 | try c.scratch.appendSlice(c.arena, &.{ | ||
| 809 | @intFromEnum(Node.Index.Optional.none), | ||
| 810 | 0, | ||
| 811 | }); | ||
| 812 | |||
| 813 | c.air_node_map[@intFromEnum(inst)] = .{ | ||
| 814 | .block = .{ .index = @intCast(scratch_top) }, | ||
| 815 | }; | ||
| 816 | const block: Air.IndexList = c.air.instData(.block, inst); | ||
| 817 | const instructions = block.slice(&c.air); | ||
| 818 | for (instructions[0 .. instructions.len - 1]) |item| { | ||
| 819 | try c.codeGenInst(item); | ||
| 820 | } | ||
| 821 | |||
| 822 | const last_break: Air.Break = c.air.instData(.@"break", instructions[instructions.len - 1]); | ||
| 823 | const last_break_operand = try c.fetchExpression(last_break.value); | ||
| 824 | |||
| 825 | try c.scratch.append(c.arena, @intFromEnum(last_break_operand)); | ||
| 826 | |||
| 827 | const items = c.scratch.items[scratch_top + 2 ..]; | ||
| 828 | const top = c.extra.list.items.len; | ||
| 829 | defer c.scratch.shrinkRetainingCapacity(scratch_top); | ||
| 830 | assert(items.len > 0); | ||
| 831 | if (items.len == 1) { | ||
| 832 | return @enumFromInt(items[0]); | ||
| 833 | } | ||
| 834 | try c.extra.list.appendSlice(c.gpa, items); | ||
| 835 | return try c.add(.comma_chain, .{ | ||
| 836 | .start = @intCast(top), | ||
| 837 | .len = @intCast(items.len), | ||
| 838 | }); | ||
| 839 | } | ||
| 840 | |||
| 752 | // Ideally, we convert everything to an expression. This allows for much | 841 | // Ideally, we convert everything to an expression. This allows for much |
| 753 | // more compact code. When that isnt possible (`return`, `while`, etc), | 842 | // more compact code. When that isnt possible (`return`, `while`, etc), |
| 754 | // we detect that here and ensure to lower `if` to actual if statements, | 843 | // we detect that here and ensure to lower `if` to actual if statements, |
| ... | @@ -774,6 +863,8 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { | ... | @@ -774,6 +863,8 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { |
| 774 | .eq, | 863 | .eq, |
| 775 | .lt, | 864 | .lt, |
| 776 | .gt, | 865 | .gt, |
| 866 | .ge, | ||
| 867 | .le, | ||
| 777 | .negate, | 868 | .negate, |
| 778 | .bool_not, | 869 | .bool_not, |
| 779 | .store, | 870 | .store, |
| ... | @@ -787,15 +878,28 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { | ... | @@ -787,15 +878,28 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { |
| 787 | // Later, this could be made true if we can hoist temporaries up. | 878 | // Later, this could be made true if we can hoist temporaries up. |
| 788 | .memory => false, | 879 | .memory => false, |
| 789 | 880 | ||
| 790 | // break/return must be a js statement | 881 | // these must be lowered to js statements |
| 791 | .@"break", | 882 | .@"break", .panic => false, |
| 792 | => false, | ||
| 793 | 883 | ||
| 884 | // A block can become a comma expression if all expressions | ||
| 885 | // are lowerable. The break at the end just becomes the result. | ||
| 794 | .block => { | 886 | .block => { |
| 795 | // TODO: blocks that are | 887 | const block: Air.IndexList = c.air.instData(.block, index); |
| 796 | // - all statements except last are lowerable | 888 | const items = block.slice(&c.air); |
| 797 | // - the last statement is lowerable OR a break of this block | 889 | for (items[0 .. items.len - 1]) |item| { |
| 798 | return false; | 890 | if (c.air.instFlags(item).uses == .multi) |
| 891 | return false; | ||
| 892 | if (!c.canLowerToExpression(item.toRef())) | ||
| 893 | return false; | ||
| 894 | } | ||
| 895 | const last_item = items[items.len - 1]; | ||
| 896 | if (c.air.instTag(last_item) != .@"break") { | ||
| 897 | return c.canLowerToExpression(last_item.toRef()); | ||
| 898 | } | ||
| 899 | const brk = c.air.instData(.@"break", last_item); | ||
| 900 | // The last break will only be lowerable if it | ||
| 901 | // breaks out of this specific block | ||
| 902 | return brk.block == index; | ||
| 799 | }, | 903 | }, |
| 800 | 904 | ||
| 801 | .@"if" => { | 905 | .@"if" => { |
| ... | @@ -818,6 +922,9 @@ fn lowerToExpression(c: *NodeGen, ref: Air.Inst.Ref) !Node.Index { | ... | @@ -818,6 +922,9 @@ fn lowerToExpression(c: *NodeGen, ref: Air.Inst.Ref) !Node.Index { |
| 818 | return switch (ref.decode()) { | 922 | return switch (ref.decode()) { |
| 819 | .value => |v| try c.valueToExpression(v), | 923 | .value => |v| try c.valueToExpression(v), |
| 820 | .index => |index| { | 924 | .index => |index| { |
| 925 | if (c.air.instTag(index) == .block) { | ||
| 926 | return try c.codeGenBlockAsExpression(index); | ||
| 927 | } | ||
| 821 | try c.codeGenInst(index); | 928 | try c.codeGenInst(index); |
| 822 | return c.fetchExpression(ref); | 929 | return c.fetchExpression(ref); |
| 823 | }, | 930 | }, |
src/core/js/Tree.zig+18| ... | @@ -39,6 +39,10 @@ pub const Tag = enum { | ... | @@ -39,6 +39,10 @@ pub const Tag = enum { |
| 39 | eq, | 39 | eq, |
| 40 | lt, | 40 | lt, |
| 41 | gt, | 41 | gt, |
| 42 | ge, | ||
| 43 | le, | ||
| 44 | @"and", | ||
| 45 | @"or", | ||
| 42 | // Exprs | 46 | // Exprs |
| 43 | call, | 47 | call, |
| 44 | js_new, | 48 | js_new, |
| ... | @@ -47,6 +51,7 @@ pub const Tag = enum { | ... | @@ -47,6 +51,7 @@ pub const Tag = enum { |
| 47 | object_literal, | 51 | object_literal, |
| 48 | array, | 52 | array, |
| 49 | index, | 53 | index, |
| 54 | comma_chain, | ||
| 50 | // Prefix ops | 55 | // Prefix ops |
| 51 | negate, | 56 | negate, |
| 52 | bool_not, | 57 | bool_not, |
| ... | @@ -63,6 +68,8 @@ pub const Tag = enum { | ... | @@ -63,6 +68,8 @@ pub const Tag = enum { |
| 63 | stmt_return, | 68 | stmt_return, |
| 64 | /// `...: { ... }` | 69 | /// `...: { ... }` |
| 65 | stmt_block, | 70 | stmt_block, |
| 71 | /// `throw x` | ||
| 72 | stmt_throw, | ||
| 66 | 73 | ||
| 67 | pub fn Data(comptime tag: Tag) type { | 74 | pub fn Data(comptime tag: Tag) type { |
| 68 | return switch (tag) { | 75 | return switch (tag) { |
| ... | @@ -83,7 +90,11 @@ pub const Tag = enum { | ... | @@ -83,7 +90,11 @@ pub const Tag = enum { |
| 83 | .eq, | 90 | .eq, |
| 84 | .lt, | 91 | .lt, |
| 85 | .gt, | 92 | .gt, |
| 93 | .ge, | ||
| 94 | .le, | ||
| 86 | .index, | 95 | .index, |
| 96 | .@"and", | ||
| 97 | .@"or", | ||
| 87 | => Bin, | 98 | => Bin, |
| 88 | .ternary => Ternary, | 99 | .ternary => Ternary, |
| 89 | .negate, | 100 | .negate, |
| ... | @@ -95,9 +106,11 @@ pub const Tag = enum { | ... | @@ -95,9 +106,11 @@ pub const Tag = enum { |
| 95 | .array => IndexList, | 106 | .array => IndexList, |
| 96 | .call, .js_new => Call, | 107 | .call, .js_new => Call, |
| 97 | .field => Field, | 108 | .field => Field, |
| 109 | .comma_chain => IndexList, | ||
| 98 | .stmt_if => StmtIf, | 110 | .stmt_if => StmtIf, |
| 99 | .stmt_break => StmtBreak, | 111 | .stmt_break => StmtBreak, |
| 100 | .stmt_return => StmtReturn, | 112 | .stmt_return => StmtReturn, |
| 113 | .stmt_throw => StmtThrow, | ||
| 101 | .stmt_declare_local => StmtDeclareLocal, | 114 | .stmt_declare_local => StmtDeclareLocal, |
| 102 | .stmt_block => StmtBlock, | 115 | .stmt_block => StmtBlock, |
| 103 | }; | 116 | }; |
| ... | @@ -261,6 +274,11 @@ pub const StmtReturn = struct { | ... | @@ -261,6 +274,11 @@ pub const StmtReturn = struct { |
| 261 | unused: Unused = .zero, | 274 | unused: Unused = .zero, |
| 262 | }; | 275 | }; |
| 263 | 276 | ||
| 277 | pub const StmtThrow = struct { | ||
| 278 | operand: Node.Index, | ||
| 279 | unused: Unused = .zero, | ||
| 280 | }; | ||
| 281 | |||
| 264 | pub const StmtBlock = struct { | 282 | pub const StmtBlock = struct { |
| 265 | label: u32, | 283 | label: u32, |
| 266 | content: ExtraIndex(IndexList), | 284 | content: ExtraIndex(IndexList), |
src/deps/quickjs/build.zig+2-2| ... | @@ -136,7 +136,7 @@ pub fn build(b: *Build) !void { | ... | @@ -136,7 +136,7 @@ pub fn build(b: *Build) !void { |
| 136 | .optimize = optimize, | 136 | .optimize = optimize, |
| 137 | .link_libc = true, | 137 | .link_libc = true, |
| 138 | }); | 138 | }); |
| 139 | if (pthread.pthreads) |pt| translate.addIncludeDir(pt.include.getPath(b)); | 139 | if (pthread.pthreads) |pt| translate.addIncludePath(pt.include); |
| 140 | const module = translate.addModule("quickjs"); | 140 | const module = translate.addModule("quickjs"); |
| 141 | module.linkLibrary(libquickjs); | 141 | module.linkLibrary(libquickjs); |
| 142 | 142 | ||
| ... | @@ -147,7 +147,7 @@ pub fn build(b: *Build) !void { | ... | @@ -147,7 +147,7 @@ pub fn build(b: *Build) !void { |
| 147 | .link_libc = true, | 147 | .link_libc = true, |
| 148 | }); | 148 | }); |
| 149 | const module2 = translate_libc.addModule("quickjs-libc"); | 149 | const module2 = translate_libc.addModule("quickjs-libc"); |
| 150 | if (pthread.pthreads) |pt| translate_libc.addIncludeDir(pt.include.getPath(b)); | 150 | if (pthread.pthreads) |pt| translate_libc.addIncludePath(pt.include); |
| 151 | module2.linkLibrary(libquickjs); | 151 | module2.linkLibrary(libquickjs); |
| 152 | module2.linkLibrary(libquickjs_libc); | 152 | module2.linkLibrary(libquickjs_libc); |
| 153 | } | 153 | } |
test/behavior/array+2-2| ... | @@ -1,8 +1,7 @@ | ... | @@ -1,8 +1,7 @@ |
| 1 | let assertEqual = @extern("assertEqual"); | 1 | let assertEqual = @extern("assertEqual"); |
| 2 | 2 | ||
| 3 | fn main { | 3 | fn main { |
| 4 | let array = [i(9), i(3), i(8), i(3), i(-1)]; | 4 | let array = a([9, 3, 8, 3, -1]); |
| 5 | |||
| 6 | assertEqual(array.len, i(5)); | 5 | assertEqual(array.len, i(5)); |
| 7 | assertEqual(array[0], i(9)); | 6 | assertEqual(array[0], i(9)); |
| 8 | assertEqual(array[1], i(3)); | 7 | assertEqual(array[1], i(3)); |
| ... | @@ -12,3 +11,4 @@ fn main { | ... | @@ -12,3 +11,4 @@ fn main { |
| 12 | } | 11 | } |
| 13 | 12 | ||
| 14 | fn i(a: int) { a } | 13 | fn i(a: int) { a } |
| 14 | fn a(a: []int) { a } |
test/compiler_tests.zig+1-1| ... | @@ -506,7 +506,7 @@ pub fn panic(msg: []const u8, trace: ?*const std.builtin.StackTrace, first_trace | ... | @@ -506,7 +506,7 @@ pub fn panic(msg: []const u8, trace: ?*const std.builtin.StackTrace, first_trace |
| 506 | if (current_case) |case| { | 506 | if (current_case) |case| { |
| 507 | std.debug.print("Crash during case: {s}\n", .{case.display}); | 507 | std.debug.print("Crash during case: {s}\n", .{case.display}); |
| 508 | } | 508 | } |
| 509 | std.debug.panicImpl(trace, first_trace_addr, msg); | 509 | std.debug.defaultPanic(msg, trace, first_trace_addr); |
| 510 | } | 510 | } |
| 511 | 511 | ||
| 512 | const std = @import("std"); | 512 | const std = @import("std"); |