| author | |
| committer | |
| log | b8d2bfd42997aba8f8e403b622fb3c8e4075a3f6 |
| tree | 03858e3bb72820546a0e23ec7758dbdb970fa801 |
| parent | 231e17437dd3075f6f42db511ca38181c3c84fb2 |
| signature | Signed by PGP key B5690EEEBB952194 |
In order to implement structs, some architectural changes have been made
in the compiler.
- Remove `codegen` as a field on `Comp.Decl`, since struct
declarations do not have any codegen. This is also good to do now,
since codegen truly lives per-`Value`, not per-`Decl`. This is critical
for lambda functions, as there will be declarations with multiple
codegens.
- Abstract the JS backend into it's own container. There are currently
no other backends, but since codegen was being re-worked, this felt
pretty reasonable too. We now have `core.Js` to represent the js
backend, and `JsLink`, `JsTree`, and `JsNodeGen` are all moved into the
`Js` namespace with their prefix removed. So Node generation is now in
`core.Js.NodeGen`. This `Js` parent struct contains backend state,
which is currently just a map of `Value` -> `Js.Tree` (moved from
`Decl.codegen`)
- Change `Js.Tree.decl_deps` to `Js.Tree.top_level_value_deps`, as the
dependencies are not on the declarations, but the associated `Value`
objects.
- Air `field` was renamed to `field_opaque`, with its behavior being
solidified as how fields work for opaque values.
Parsing for inferred structs is implemented but there is a TODO at
the Transform pass.
29 files changed, 4031 insertions(+), 2247 deletions(-)
CHANGELOG.md+4| ... | ... | @@ -1,8 +1,12 @@ |
| 1 | 1 | ## 0.1.4 |
| 2 | 2 | |
| 3 | - feat: implement string identifiers | |
| 4 | - feat: implement basic struct types | |
| 5 | - feat: include a `README.html` alongside compiler with documentation and code samples. | |
| 3 | 6 | - fix(sema): miscompilation using left side of all binary expression for both sides |
| 4 | 7 | - fix(sema): handle recursive function calls |
| 5 | 8 | - fix(sema): quit block analysis when hitting `noreturn` |
| 9 | - fix(sema): binary type resolution with opaque and numeric literals no longer crash | |
| 6 | 10 | |
| 7 | 11 | ## 0.1.3 |
| 8 | 12 |
README.md+8-3| ... | ... | @@ -1,15 +1,20 @@ |
| 1 | 1 | # New Language Project |
| 2 | 2 | |
| 3 | This is a high-level, general purpose programming language. It is a mix of Rust, Zig, and JavaScript's great points. | |
| 3 | This is a high-level, general purpose programming language. It is a mix of Rust, Zig, and | |
| 4 | JavaScript's great points. | |
| 4 | 5 | |
| 5 | For a guide on how to use the language, please see the Language Reference document. | |
| 6 | For a guide on how to use the language, please see the Language Manual included in the binary | |
| 7 | distribution. (TODO: host a rendered version) | |
| 6 | 8 | |
| 7 | 9 | ## Building |
| 8 | 10 | |
| 9 | The primary dependency of the language SDK is a Zig compiler. If one is not available, the `./build` and `.\build.cmd` scripts can substitute for running `zig build` on POSIX and Windows systems, respectively. With a Zig compiler, a release build can be produced in a single command: | |
| 11 | The primary dependency of the language SDK is a Zig compiler. If one is not available, the | |
| 12 | `./build` and `.\build.cmd` scripts can substitute for running `zig build` on POSIX and Windows | |
| 13 | systems, respectively. With a Zig compiler, a release build can be produced in a single command: | |
| 10 | 14 | |
| 11 | 15 | zig build --release |
| 12 | 16 | |
| 13 | 17 | To run the standard packaging procedure, which builds the SDK for all officially supported targets: |
| 14 | 18 | |
| 15 | 19 | zig build package |
| 20 |
build.zig+18-6| ... | ... | @@ -34,8 +34,14 @@ pub fn build(b: *std.Build) void { |
| 34 | 34 | "Enable tracing", |
| 35 | 35 | ) orelse .none; |
| 36 | 36 | |
| 37 | const native_debug = buildWithTarget(b, native_target, .Debug, .none); | |
| 38 | const main = buildWithTarget(b, target, optimize, tracing); | |
| 37 | const no_llvm = b.option( | |
| 38 | bool, | |
| 39 | "no_llvm", | |
| 40 | "Experiment with the Zig self-hosted backends", | |
| 41 | ) orelse false; | |
| 42 | ||
| 43 | const native_debug = buildWithTarget(b, native_target, .Debug, .none, no_llvm); | |
| 44 | const main = buildWithTarget(b, target, optimize, tracing, no_llvm); | |
| 39 | 45 | |
| 40 | 46 | b.installArtifact(main.compiler); |
| 41 | 47 | |
| ... | ... | @@ -46,7 +52,6 @@ pub fn build(b: *std.Build) void { |
| 46 | 52 | const artifact, const name = entry; |
| 47 | 53 | |
| 48 | 54 | const run_cmd = b.addRunArtifact(artifact); |
| 49 | run_cmd.step.dependOn(b.getInstallStep()); | |
| 50 | 55 | if (b.args) |args| { |
| 51 | 56 | run_cmd.addArgs(args); |
| 52 | 57 | } |
| ... | ... | @@ -128,7 +133,7 @@ pub fn build(b: *std.Build) void { |
| 128 | 133 | }, |
| 129 | 134 | ) catch @panic("OOM"); |
| 130 | 135 | |
| 131 | const artifacts = buildWithTarget(b, cross_target, .ReleaseSafe, .none); | |
| 136 | const artifacts = buildWithTarget(b, cross_target, .ReleaseSafe, .none, no_llvm); | |
| 132 | 137 | |
| 133 | 138 | inline for (.{ |
| 134 | 139 | .{ artifacts.compiler, "compiler" }, |
| ... | ... | @@ -168,8 +173,11 @@ fn buildWithTarget( |
| 168 | 173 | target: Build.ResolvedTarget, |
| 169 | 174 | optimize: std.builtin.OptimizeMode, |
| 170 | 175 | tracing: TracingMode, |
| 176 | no_llvm: bool, | |
| 171 | 177 | ) Artifacts { |
| 172 | 178 | const dep_opts = .{ .target = target, .optimize = optimize }; |
| 179 | const use_llvm = if (no_llvm) false else null; | |
| 180 | const use_lld = if (no_llvm) false else null; | |
| 173 | 181 | |
| 174 | 182 | // Dependencies |
| 175 | 183 | const zg = b.dependency("zg", dep_opts); |
| ... | ... | @@ -195,6 +203,8 @@ fn buildWithTarget( |
| 195 | 203 | .root_source_file = b.path("src/core/mod.zig"), |
| 196 | 204 | .target = target, |
| 197 | 205 | .optimize = optimize, |
| 206 | .use_lld = use_lld, | |
| 207 | .use_llvm = use_llvm, | |
| 198 | 208 | }); |
| 199 | 209 | core_tests.root_module.addImport("build_options", opts_mod); |
| 200 | 210 | core_tests.root_module.addImport("tracer", tracer.module("tracer")); |
| ... | ... | @@ -206,7 +216,8 @@ fn buildWithTarget( |
| 206 | 216 | .root_source_file = b.path("src/core/main.zig"), |
| 207 | 217 | .target = target, |
| 208 | 218 | .optimize = optimize, |
| 209 | .link_libc = true, | |
| 219 | .use_lld = use_lld, | |
| 220 | .use_llvm = use_llvm, | |
| 210 | 221 | }); |
| 211 | 222 | compiler_exe.root_module.addImport("build_options", opts_mod); |
| 212 | 223 | compiler_exe.root_module.addImport("core", core); |
| ... | ... | @@ -218,7 +229,8 @@ fn buildWithTarget( |
| 218 | 229 | .root_source_file = b.path("src/lsp/lsp.zig"), |
| 219 | 230 | .target = target, |
| 220 | 231 | .optimize = optimize, |
| 221 | .link_libc = true, | |
| 232 | .use_lld = use_lld, | |
| 233 | .use_llvm = use_llvm, | |
| 222 | 234 | }); |
| 223 | 235 | lsp_exe.root_module.addImport("build_options", opts_mod); |
| 224 | 236 | lsp_exe.root_module.addImport("core", core); |
doc/architecture.md+10-4| ... | ... | @@ -1,7 +1,13 @@ |
| 1 | # compiler architecture | |
| 1 | # Architecture | |
| 2 | 2 | |
| 3 | 3 | This document will outline the code structure of the language's SDK as well as the compilation pipeline architecture. The major components of the language are: |
| 4 | 4 | |
| 5 | - [**Core**][core] | |
| 6 | - [**Standard Library**][std] | |
| 7 | - [**Language Server**][lsp] | |
| 5 | - [**Core**][core] (`src/core`) | |
| 6 | - [**SDK Manual Generator**][manual] (`src/manual`) | |
| 7 | ||
| 8 | Keep in mind that code is the best documentation for specific details, and this document aims to give an overall explanation of the high level process. The entry point to each component is `mod.zig`. | |
| 9 | ||
| 10 | General code style is to sort the file's contents by importance. Most files will start with an overview comment, followed by the root struct fields, and then important companion types and functions, then helpers. Imports and tests are not as important to a reader, so those are placed at the end of the file. | |
| 11 | ||
| 12 | An example of this organization is `src/core/Tokenizer.zig`, which opens with the tokenizer state, and then `pub fn tokenize` to bring attention to the flow of tokenization. The `Token` struct is directly after this, since it is important, but not as important as visualizing *how* the tokenizer works. | |
| 13 |
doc/language-manual.md+40| ... | ... | @@ -158,3 +158,43 @@ fn currentYear() -> int { |
| 158 | 158 | |
| 159 | 159 | print(currentYear()); |
| 160 | 160 | ``` |
| 161 | ||
| 162 | ## Structs | |
| 163 | ||
| 164 | To group data together, a `struct` can be used. A struct contains zero or one fields of runtime-compatible types. | |
| 165 | ||
| 166 | ```lang:run | |
| 167 | let print = @js("console").log; | |
| 168 | ||
| 169 | struct Position { | |
| 170 | x: int, | |
| 171 | y: int, | |
| 172 | } | |
| 173 | ||
| 174 | let center = Position{ x: 0, y: -2 }; | |
| 175 | print(center.x, center.y); | |
| 176 | ||
| 177 | fn addPositions(a: Position, b: Position) -> Position { | |
| 178 | // To allow less verbose code, the struct type can be inferred with `.` | |
| 179 | // TODO: that is not implemented ;-; | |
| 180 | Position{ x: a.x + b.x, y: a.y + b.y } | |
| 181 | } | |
| 182 | ||
| 183 | let added = addPositions( | |
| 184 | Position{ x: 1, y: -4 }, | |
| 185 | Position{ x: 5, y: 8 }, | |
| 186 | ); | |
| 187 | print("Add result:", added.x, added.y); | |
| 188 | ``` | |
| 189 | ||
| 190 | To allow optimizations, `struct`s by default do not have a defined representation in JavaScript. This means passing the struct to an opaque function is not allowed. | |
| 191 | ||
| 192 | ```lang:build | |
| 193 | struct Person { | |
| 194 | name: string, | |
| 195 | age: int, | |
| 196 | } | |
| 197 | ||
| 198 | let print = @js("console").log; | |
| 199 | print(Person{ name: "hello", age: 42 }); | |
| 200 | ``` |
src/core/Air.Dump.zig+96-4| ... | ... | @@ -116,8 +116,8 @@ pub const State = struct { |
| 116 | 116 | try s.dumpValue(w, memory.ty.value()); |
| 117 | 117 | try w.writeAll(")"); |
| 118 | 118 | }, |
| 119 | .field => { | |
| 120 | const field: Air.Field = air.instData(.field, inst); | |
| 119 | .field_opaque => { | |
| 120 | const field: Air.FieldOpaque = air.instData(.field_opaque, inst); | |
| 121 | 121 | try w.writeAll("("); |
| 122 | 122 | try s.dumpRef(w, field.base); |
| 123 | 123 | try w.writeAll(", "); |
| ... | ... | @@ -126,6 +126,22 @@ pub const State = struct { |
| 126 | 126 | try tty.setColor(w, .reset); |
| 127 | 127 | try w.writeAll(")"); |
| 128 | 128 | }, |
| 129 | .field_struct => { | |
| 130 | const field: Air.FieldStruct = air.instData(.field_struct, inst); | |
| 131 | try w.writeAll("("); | |
| 132 | try s.dumpInstRef(w, field.base); | |
| 133 | try w.writeAll(", "); | |
| 134 | try tty.setColor(w, .magenta); | |
| 135 | try w.print("{s}", .{ | |
| 136 | air | |
| 137 | .typeOfIndex(field.base, s.ip) | |
| 138 | .toStructType(s.ip).? | |
| 139 | .field_keys[field.field_index] | |
| 140 | .toSlice(s.ip), | |
| 141 | }); | |
| 142 | try tty.setColor(w, .reset); | |
| 143 | try w.writeAll(")"); | |
| 144 | }, | |
| 129 | 145 | inline .call, .new => |t| { |
| 130 | 146 | const call: Air.Call = air.instData(t, inst); |
| 131 | 147 | const call_data = call.call_data.get(air); |
| ... | ... | @@ -152,6 +168,23 @@ pub const State = struct { |
| 152 | 168 | try s.newline(w); |
| 153 | 169 | try w.writeAll("}"); |
| 154 | 170 | }, |
| 171 | .struct_init => { | |
| 172 | const struct_init: Air.StructInit = air.instData(.struct_init, inst); | |
| 173 | const struct_type = struct_init.ty.toStructType(s.ip).?; | |
| 174 | ||
| 175 | try w.writeAll("("); | |
| 176 | try s.dumpValue(w, struct_init.ty.value()); | |
| 177 | try w.writeAll(", {"); | |
| 178 | s.indent += 1; | |
| 179 | for (struct_type.field_keys, struct_init.valuesPtr(air)) |key, ref| { | |
| 180 | try s.newline(w); | |
| 181 | try w.print("{}: ", .{key.fmt(s.ip)}); | |
| 182 | try s.dumpRef(w, ref); | |
| 183 | } | |
| 184 | s.indent -= 1; | |
| 185 | try s.newline(w); | |
| 186 | try w.writeAll("})"); | |
| 187 | }, | |
| 155 | 188 | } |
| 156 | 189 | } |
| 157 | 190 | |
| ... | ... | @@ -188,14 +221,14 @@ pub const State = struct { |
| 188 | 221 | try w.print("{s}", .{@tagName(t)}); |
| 189 | 222 | try tty.setColor(w, .reset); |
| 190 | 223 | }, |
| 191 | .type_function => |ty| { | |
| 224 | .function_type => |ty| { | |
| 192 | 225 | try tty.setColor(w, .blue); |
| 193 | 226 | try w.writeAll("fn("); |
| 194 | 227 | try tty.setColor(w, .reset); |
| 195 | 228 | for (ty.param_types, 0..) |param, i| { |
| 196 | 229 | if (i != 0) { |
| 197 | 230 | try tty.setColor(w, .blue); |
| 198 | try w.writeAll(","); | |
| 231 | try w.writeAll(", "); | |
| 199 | 232 | try tty.setColor(w, .reset); |
| 200 | 233 | } |
| 201 | 234 | try s.dumpValue(w, param.value()); |
| ... | ... | @@ -245,6 +278,65 @@ pub const State = struct { |
| 245 | 278 | try w.writeAll(">"); |
| 246 | 279 | try tty.setColor(w, .reset); |
| 247 | 280 | }, |
| 281 | .struct_type => |struct_type| { | |
| 282 | try tty.setColor(w, .bright_magenta); | |
| 283 | try w.writeAll("struct"); // TODO: struct name | |
| 284 | try tty.setColor(w, .reset); | |
| 285 | try w.writeAll(" {"); | |
| 286 | ||
| 287 | if (struct_type.field_keys.len > 0) | |
| 288 | try w.writeAll(" "); | |
| 289 | ||
| 290 | for ( | |
| 291 | struct_type.field_keys, | |
| 292 | struct_type.field_types, | |
| 293 | 0.., | |
| 294 | ) |key, field_type, i| { | |
| 295 | if (i != 0) | |
| 296 | try w.writeAll(", "); | |
| 297 | try w.writeAll(key.toSlice(s.ip)); | |
| 298 | try w.writeAll(": "); | |
| 299 | try s.dumpValue(w, field_type.value()); | |
| 300 | } | |
| 301 | ||
| 302 | if (struct_type.field_keys.len > 0) | |
| 303 | try w.writeAll(" "); | |
| 304 | ||
| 305 | try w.writeAll("}"); | |
| 306 | }, | |
| 307 | .struct_value => |struct_value| { | |
| 308 | try tty.setColor(w, .bright_black); | |
| 309 | try w.writeAll("<"); | |
| 310 | try tty.setColor(w, .bright_magenta); | |
| 311 | try w.writeAll("struct"); // TODO: struct name | |
| 312 | try tty.setColor(w, .bright_black); | |
| 313 | try w.writeAll(", "); | |
| 314 | try tty.setColor(w, .reset); | |
| 315 | try w.writeAll("{"); | |
| 316 | ||
| 317 | if (struct_value.field_values.len > 0) | |
| 318 | try w.writeAll(" "); | |
| 319 | ||
| 320 | for ( | |
| 321 | struct_value.ty.toStructType(s.ip).?.field_keys, | |
| 322 | struct_value.field_values, | |
| 323 | 0.., | |
| 324 | ) |key, value, i| { | |
| 325 | if (i != 0) | |
| 326 | try w.writeAll(", "); | |
| 327 | try w.writeAll(key.toSlice(s.ip)); | |
| 328 | try w.writeAll(": "); | |
| 329 | try s.dumpValue(w, value); | |
| 330 | } | |
| 331 | ||
| 332 | if (struct_value.field_values.len > 0) | |
| 333 | try w.writeAll(" "); | |
| 334 | ||
| 335 | try w.writeAll("}"); | |
| 336 | try tty.setColor(w, .bright_black); | |
| 337 | try w.writeAll(">"); | |
| 338 | try tty.setColor(w, .reset); | |
| 339 | }, | |
| 248 | 340 | } |
| 249 | 341 | } |
| 250 | 342 |
src/core/Air.zig+101-24| ... | ... | @@ -20,11 +20,15 @@ pub const Inst = struct { |
| 20 | 20 | @"break", |
| 21 | 21 | @"if", |
| 22 | 22 | arg, |
| 23 | field, | |
| 24 | 23 | |
| 25 | 24 | memory, |
| 26 | 25 | store, |
| 27 | 26 | |
| 27 | struct_init, | |
| 28 | ||
| 29 | field_opaque, | |
| 30 | field_struct, | |
| 31 | ||
| 28 | 32 | // bin |
| 29 | 33 | add, |
| 30 | 34 | sub, |
| ... | ... | @@ -48,9 +52,11 @@ pub const Inst = struct { |
| 48 | 52 | .@"if" => If, |
| 49 | 53 | .call, .new => Call, |
| 50 | 54 | .arg => Arg, |
| 51 | .field => Field, | |
| 52 | 55 | .memory => Memory, |
| 53 | 56 | .store => Store, |
| 57 | .struct_init => StructInit, | |
| 58 | .field_opaque => FieldOpaque, | |
| 59 | .field_struct => FieldStruct, | |
| 54 | 60 | .add, |
| 55 | 61 | .sub, |
| 56 | 62 | .mul, |
| ... | ... | @@ -83,14 +89,26 @@ pub const Inst = struct { |
| 83 | 89 | }; |
| 84 | 90 | } |
| 85 | 91 | |
| 86 | pub fn toOptional(ref: ?Ref) Optional { | |
| 87 | return if (ref) |r| @enumFromInt(@as(u32, @bitCast(r))) else .none; | |
| 92 | pub fn value(ref: Ref) ?Value { | |
| 93 | return if (ref.kind == .value) return @enumFromInt(ref.data) else null; | |
| 94 | } | |
| 95 | ||
| 96 | pub fn index(ref: Ref) ?Value { | |
| 97 | return if (ref.kind == .value) return @enumFromInt(ref.data) else null; | |
| 98 | } | |
| 99 | ||
| 100 | pub fn toOptional(ref: Ref) Optional { | |
| 101 | return @enumFromInt(@as(u32, @bitCast(ref))); | |
| 88 | 102 | } |
| 89 | 103 | |
| 90 | 104 | pub const Optional = enum(u32) { |
| 91 | 105 | none = std.math.maxInt(u32), |
| 92 | 106 | _, |
| 93 | 107 | |
| 108 | pub fn init(ref: ?Ref) Optional { | |
| 109 | return if (ref) |r| @enumFromInt(@as(u32, @bitCast(r))) else .none; | |
| 110 | } | |
| 111 | ||
| 94 | 112 | pub fn unwrap(opt: Optional) ?Ref { |
| 95 | 113 | return if (opt != .none) @bitCast(@intFromEnum(opt)) else null; |
| 96 | 114 | } |
| ... | ... | @@ -104,6 +122,24 @@ pub const Inst = struct { |
| 104 | 122 | pub fn eql(ref: Ref, other: Ref) bool { |
| 105 | 123 | return @as(u32, @bitCast(ref)) == @as(u32, @bitCast(other)); |
| 106 | 124 | } |
| 125 | ||
| 126 | pub fn dump(ref: Ref, ip: *const InternPool) std.fmt.Formatter(dumpImpl) { | |
| 127 | return .{ .data = .{ ref, ip } }; | |
| 128 | } | |
| 129 | ||
| 130 | fn dumpImpl( | |
| 131 | data: struct { Ref, *const InternPool }, | |
| 132 | comptime fmt: []const u8, | |
| 133 | _: std.fmt.FormatOptions, | |
| 134 | w: anytype, | |
| 135 | ) !void { | |
| 136 | comptime assert(fmt.len == 0); | |
| 137 | const ref, const ip = data; | |
| 138 | switch (ref.decode()) { | |
| 139 | .value => |v| try w.print("{}", .{v.dump(ip)}), | |
| 140 | .index => |i| try w.print("#{d}", .{@intFromEnum(i)}), | |
| 141 | } | |
| 142 | } | |
| 107 | 143 | }; |
| 108 | 144 | |
| 109 | 145 | /// reference to another instruction in this air |
| ... | ... | @@ -189,7 +225,7 @@ pub const Un = struct { |
| 189 | 225 | unused: Unused = .zero, |
| 190 | 226 | }; |
| 191 | 227 | |
| 192 | pub const Field = struct { | |
| 228 | pub const FieldOpaque = struct { | |
| 193 | 229 | base: Inst.Ref, |
| 194 | 230 | field: InternPool.NullTerminatedString, |
| 195 | 231 | }; |
| ... | ... | @@ -224,6 +260,23 @@ pub const Memory = struct { |
| 224 | 260 | ty: Type, |
| 225 | 261 | }; |
| 226 | 262 | |
| 263 | pub const StructInit = struct { | |
| 264 | ty: Type, | |
| 265 | field_values: u32, | |
| 266 | ||
| 267 | pub fn valuesPtr( | |
| 268 | init: *const StructInit, | |
| 269 | air: *const Air, | |
| 270 | ) [*]const Air.Inst.Ref { | |
| 271 | return @ptrCast(air.extra.list.items[init.field_values..].ptr); | |
| 272 | } | |
| 273 | }; | |
| 274 | ||
| 275 | pub const FieldStruct = struct { | |
| 276 | base: Inst.Index, | |
| 277 | field_index: u32, | |
| 278 | }; | |
| 279 | ||
| 227 | 280 | pub fn deinit(air: *Air, gpa: std.mem.Allocator) void { |
| 228 | 281 | defer air.* = undefined; |
| 229 | 282 | air.insts.deinit(gpa); |
| ... | ... | @@ -250,46 +303,70 @@ pub fn instPayload(air: *const Air, comptime tag: Inst.Tag, index: Inst.Index) t |
| 250 | 303 | return air.instData(tag, index).extra_index.get(air); |
| 251 | 304 | } |
| 252 | 305 | |
| 253 | pub fn typeOf(air: *const Air, ref: Inst.Ref, ip: *InternPool) InternPool.Type { | |
| 306 | pub fn typeOf(air: *const Air, ref: Inst.Ref, ip: *const InternPool) InternPool.Type { | |
| 254 | 307 | return switch (ref.decode()) { |
| 255 | 308 | .value => |v| v.typeOf(ip), |
| 256 | 309 | .index => |i| air.typeOfIndex(i, ip), |
| 257 | 310 | }; |
| 258 | 311 | } |
| 259 | 312 | |
| 260 | pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *InternPool) InternPool.Type { | |
| 313 | pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) InternPool.Type { | |
| 261 | 314 | return switch (air.instTag(index)) { |
| 315 | // These values are by definition, opaque | |
| 262 | 316 | .js_global_ref, .new => .@"opaque", |
| 263 | 317 | |
| 264 | .block => { | |
| 265 | const items = air.instData(.block, index).slice(air); | |
| 266 | return air.typeOfIndex(items[items.len - 1], ip); | |
| 267 | }, | |
| 268 | ||
| 269 | inline .add, .sub, .mul, .div => |t| air.typeOf(air.instData(t, index).lhs, ip), | |
| 270 | ||
| 318 | // These operations always produce known types | |
| 271 | 319 | .lt, .gt, .eq, .bool_not => .bool, |
| 320 | .@"break" => .noreturn, | |
| 321 | .field_opaque => .@"opaque", | |
| 272 | 322 | |
| 323 | // For many instructions, the type is stored on it's data directly. This | |
| 324 | // is avoided when possible to reduce the size of Air. | |
| 325 | .memory => air.instData(.memory, index).ty, | |
| 326 | .store => air.instData(.memory, air.instData(.store, index).memory).ty, | |
| 327 | .@"if" => air.instData(.@"if", index).data.get(air).union_ty, | |
| 328 | .struct_init => air.instData(.struct_init, index).ty, | |
| 273 | 329 | .arg => air.instData(.arg, index).type, |
| 274 | 330 | |
| 331 | // A handful of binary operators are lowered to a form where both sides | |
| 332 | // are the same type, so the type of the left side can be used. | |
| 333 | inline .add, | |
| 334 | .sub, | |
| 335 | .mul, | |
| 336 | .div, | |
| 337 | => |t| { | |
| 338 | const left = air.typeOf(air.instData(t, index).lhs, ip); | |
| 339 | if (std.debug.runtime_safety) { | |
| 340 | assert(left == air.typeOf(air.instData(t, index).rhs, ip)); | |
| 341 | } | |
| 342 | return left; | |
| 343 | }, | |
| 344 | // Negate follows a similar pattern to the above | |
| 275 | 345 | .negate => air.typeOf(air.instData(.negate, index).operand, ip), |
| 276 | 346 | |
| 277 | .@"break" => .noreturn, | |
| 278 | ||
| 279 | // todo: | |
| 280 | .field => .@"opaque", | |
| 347 | // TODO: taking the last instruction is likely wrong. | |
| 348 | // The last instruction is going to be a .break and be of type .noreturn | |
| 349 | .block => { | |
| 350 | const items = air.instData(.block, index).slice(air); | |
| 351 | assert(items.len > 0); // empty blocks should always be `Value.void` | |
| 352 | return air.typeOfIndex(items[items.len - 1], ip); | |
| 353 | }, | |
| 281 | 354 | |
| 355 | // Calls return the return type of the operand. | |
| 282 | 356 | .call => { |
| 283 | 357 | const base_type = air.typeOf(air.instData(.call, index).base, ip); |
| 284 | 358 | if (base_type == .@"opaque") return .@"opaque"; |
| 285 | return base_type.value().toKey(ip).type_function.return_type; | |
| 359 | return base_type.toFunctionPrototype(ip).?.return_type; | |
| 286 | 360 | }, |
| 287 | 361 | |
| 288 | .memory => air.instData(.memory, index).ty, | |
| 289 | ||
| 290 | .store => air.instData(.memory, air.instData(.store, index).memory).ty, | |
| 291 | ||
| 292 | .@"if" => air.instData(.@"if", index).data.get(air).union_ty, | |
| 362 | // Field accesses on structs are special-cased by storing the field | |
| 363 | // index instead of the name string. This avoids creating hashmaps. | |
| 364 | .field_struct => { | |
| 365 | const inst = air.instData(.field_struct, index); | |
| 366 | const base_type = air.typeOfIndex(inst.base, ip) | |
| 367 | .toStructType(ip) orelse unreachable; // not a struct | |
| 368 | return base_type.field_types[inst.field_index]; | |
| 369 | }, | |
| 293 | 370 | }; |
| 294 | 371 | } |
| 295 | 372 |
src/core/Ast.Dump.zig+56-26| ... | ... | @@ -81,33 +81,41 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err |
| 81 | 81 | } |
| 82 | 82 | try tty.setColor(writer, .reset); |
| 83 | 83 | }, |
| 84 | .fn_decl => { | |
| 85 | try writer.writeAll(" "); | |
| 86 | const fn_decl = state.ast.fullFnDecl(node); | |
| 87 | if (fn_decl.public) { | |
| 88 | try writer.writeAll("[pub] "); | |
| 89 | } | |
| 90 | try tty.setColor(writer, .magenta); | |
| 91 | try writer.writeAll(fn_decl.id); | |
| 92 | try tty.setColor(writer, .reset); | |
| 93 | try writer.writeAll(" {"); | |
| 94 | { | |
| 95 | state.indent += 1; | |
| 96 | defer state.indent -= 1; | |
| 97 | try state.newline(writer); | |
| 98 | try writer.writeAll("params:"); | |
| 99 | try dumpNodeList(writer, state, fn_decl.params, '[', ']'); | |
| 100 | if (fn_decl.return_type) |return_type| { | |
| 84 | .function => switch (tag) { | |
| 85 | else => unreachable, | |
| 86 | inline .fn_decl, .fn_expr => |comptime_tag| { | |
| 87 | const fn_decl = if (comptime_tag == .fn_decl) | |
| 88 | state.ast.fullFnDecl(node) | |
| 89 | else | |
| 90 | state.ast.fullFnExpr(node); | |
| 91 | if (comptime_tag == .fn_decl) { | |
| 92 | try writer.writeAll(" "); | |
| 93 | if (fn_decl.public) { | |
| 94 | try writer.writeAll("[pub] "); | |
| 95 | } | |
| 96 | try tty.setColor(writer, .magenta); | |
| 97 | try writer.writeAll(fn_decl.id); | |
| 98 | try tty.setColor(writer, .reset); | |
| 99 | } | |
| 100 | try writer.writeAll(" {"); | |
| 101 | { | |
| 102 | state.indent += 1; | |
| 103 | defer state.indent -= 1; | |
| 104 | try state.newline(writer); | |
| 105 | try writer.writeAll("params:"); | |
| 106 | try dumpNodeList(writer, state, fn_decl.params, '[', ']'); | |
| 107 | if (fn_decl.return_type) |return_type| { | |
| 108 | try state.newline(writer); | |
| 109 | try writer.writeAll("return_type: "); | |
| 110 | try dumpNode(writer, state, return_type); | |
| 111 | } | |
| 101 | 112 | try state.newline(writer); |
| 102 | try writer.writeAll("return_type: "); | |
| 103 | try dumpNode(writer, state, return_type); | |
| 113 | try writer.writeAll("body: "); | |
| 114 | try dumpNode(writer, state, fn_decl.body); | |
| 104 | 115 | } |
| 105 | 116 | try state.newline(writer); |
| 106 | try writer.writeAll("body: "); | |
| 107 | try dumpNode(writer, state, fn_decl.body); | |
| 108 | } | |
| 109 | try state.newline(writer); | |
| 110 | try writer.writeAll("}"); | |
| 117 | try writer.writeAll("}"); | |
| 118 | }, | |
| 111 | 119 | }, |
| 112 | 120 | .list => { |
| 113 | 121 | const list = data.list.slice(state.ast); |
| ... | ... | @@ -115,6 +123,7 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err |
| 115 | 123 | .root, |
| 116 | 124 | .block_void, |
| 117 | 125 | .block_implicit_return, |
| 126 | .struct_decl, | |
| 118 | 127 | => try dumpNodeList(writer, state, list, '{', '}'), |
| 119 | 128 | .builtin_call, |
| 120 | 129 | => try dumpNodeList(writer, state, list, '(', ')'), |
| ... | ... | @@ -155,8 +164,10 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err |
| 155 | 164 | try tty.setColor(writer, .magenta); |
| 156 | 165 | try writer.writeAll(param_decl.id); |
| 157 | 166 | try tty.setColor(writer, .reset); |
| 158 | try writer.writeAll(": "); | |
| 159 | try dumpNode(writer, state, param_decl.type); | |
| 167 | if (param_decl.type) |ty| { | |
| 168 | try writer.writeAll(": "); | |
| 169 | try dumpNode(writer, state, ty); | |
| 170 | } | |
| 160 | 171 | }, |
| 161 | 172 | .bin => { |
| 162 | 173 | try writer.writeAll(" ("); |
| ... | ... | @@ -244,6 +255,25 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err |
| 244 | 255 | try dumpNodeList(writer, state, arg_list, '[', ']'); |
| 245 | 256 | try writer.writeAll(")"); |
| 246 | 257 | }, |
| 258 | .struct_init => { | |
| 259 | const base = data.struct_init.base; | |
| 260 | const extra = state.ast.extraData(data.struct_init.extra, Ast.SubList); | |
| 261 | const fields = extra.slice(state.ast); | |
| 262 | ||
| 263 | try writer.writeAll("("); | |
| 264 | try writer.writeAll("base: "); | |
| 265 | if (base.unwrap()) |b| { | |
| 266 | try dumpNode(writer, state, b); | |
| 267 | } else { | |
| 268 | try tty.setColor(writer, .blue); | |
| 269 | try writer.writeAll("<implicit>"); | |
| 270 | try tty.setColor(writer, .reset); | |
| 271 | } | |
| 272 | try writer.writeAll(", "); | |
| 273 | try writer.writeAll("fields:"); | |
| 274 | try dumpNodeList(writer, state, fields, '[', ']'); | |
| 275 | try writer.writeAll(")"); | |
| 276 | }, | |
| 247 | 277 | .@"if" => { |
| 248 | 278 | const branch = data.@"if"; |
| 249 | 279 | const if_data = state.ast.extraData(branch.extra, Ast.If); |
src/core/Ast.zig+169-55| ... | ... | @@ -75,6 +75,10 @@ pub const Node = struct { |
| 75 | 75 | /// `fn foo(x: i32) -> i32 { rhs }` |
| 76 | 76 | /// Params and return type are stored in extra data |
| 77 | 77 | fn_decl, |
| 78 | /// Function expression | |
| 79 | /// `fn (x, y) { x + y }` | |
| 80 | /// Params and return type are stored in extra data | |
| 81 | fn_expr, | |
| 78 | 82 | /// 'name: type' declaration |
| 79 | 83 | /// Main token is the identifier |
| 80 | 84 | param_decl, |
| ... | ... | @@ -208,6 +212,12 @@ pub const Node = struct { |
| 208 | 212 | pure, |
| 209 | 213 | /// `assert expr`. main_token is the operator. |
| 210 | 214 | assert, |
| 215 | /// Struct Declaration. main_token is the id if set | |
| 216 | struct_decl, | |
| 217 | /// Struct Field. | |
| 218 | struct_field, | |
| 219 | /// Struct Init | |
| 220 | struct_init, | |
| 211 | 221 | |
| 212 | 222 | pub fn isAssignBin(op: Tag) bool { |
| 213 | 223 | return switch (op) { |
| ... | ... | @@ -264,71 +274,77 @@ pub const Node = struct { |
| 264 | 274 | }; |
| 265 | 275 | |
| 266 | 276 | pub const data_tags = std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{ |
| 267 | .root = .list, | |
| 268 | .block_void = .list, | |
| 269 | .block_implicit_return = .list, | |
| 270 | .var_decl = .var_decl, | |
| 271 | .fn_decl = .fn_decl, | |
| 272 | .param_decl = .param_decl, | |
| 273 | .id = .none, | |
| 274 | .number_int = .none, | |
| 275 | .number_float = .none, | |
| 276 | .string = .none, | |
| 277 | .multiline_string = .list, | |
| 278 | .call = .call, | |
| 279 | .builtin_call = .list, | |
| 277 | // sort-lines:start | |
| 278 | .@"and" = .bin, | |
| 279 | .@"break" = .@"break", | |
| 280 | .@"comptime" = .un, | |
| 280 | 281 | .@"if" = .@"if", |
| 281 | .field = .field, | |
| 282 | .index = .bin, | |
| 282 | .@"or" = .bin, | |
| 283 | 283 | .@"return" = .@"return", |
| 284 | .@"break" = .@"break", | |
| 285 | .group = .un, | |
| 286 | 284 | .add = .bin, |
| 287 | .sub = .bin, | |
| 288 | .mul = .bin, | |
| 289 | .div = .bin, | |
| 290 | .rem = .bin, | |
| 291 | .pow = .bin, | |
| 292 | .@"and" = .bin, | |
| 293 | .@"or" = .bin, | |
| 285 | .add_assign = .bin, | |
| 286 | .and_assign = .bin, | |
| 287 | .assert = .un, | |
| 288 | .assign = .bin, | |
| 294 | 289 | .bit_and = .bin, |
| 290 | .bit_and_assign = .bin, | |
| 291 | .bit_not = .un, | |
| 295 | 292 | .bit_or = .bin, |
| 293 | .bit_or_assign = .bin, | |
| 296 | 294 | .bit_xor = .bin, |
| 297 | .shl = .bin, | |
| 298 | .shr = .bin, | |
| 295 | .bit_xor_assign = .bin, | |
| 296 | .block_implicit_return = .list, | |
| 297 | .block_void = .list, | |
| 298 | .bool_not = .un, | |
| 299 | .builtin_call = .list, | |
| 300 | .call = .call, | |
| 301 | .div = .bin, | |
| 302 | .div_assign = .bin, | |
| 299 | 303 | .eq = .bin, |
| 300 | .ne = .bin, | |
| 301 | .lt = .bin, | |
| 302 | .le = .bin, | |
| 303 | .gt = .bin, | |
| 304 | .field = .field, | |
| 305 | .fn_decl = .function, | |
| 306 | .fn_expr = .function, | |
| 304 | 307 | .ge = .bin, |
| 305 | .assign = .bin, | |
| 306 | .add_assign = .bin, | |
| 307 | .sub_assign = .bin, | |
| 308 | .group = .un, | |
| 309 | .gt = .bin, | |
| 310 | .id = .none, | |
| 311 | .index = .bin, | |
| 312 | .label = .label, | |
| 313 | .le = .bin, | |
| 314 | .lt = .bin, | |
| 315 | .mul = .bin, | |
| 308 | 316 | .mul_assign = .bin, |
| 309 | .div_assign = .bin, | |
| 310 | .rem_assign = .bin, | |
| 311 | .pow_assign = .bin, | |
| 312 | .and_assign = .bin, | |
| 317 | .multiline_string = .list, | |
| 318 | .ne = .bin, | |
| 319 | .negate = .un, | |
| 320 | .number_float = .none, | |
| 321 | .number_int = .none, | |
| 313 | 322 | .or_assign = .bin, |
| 314 | .bit_and_assign = .bin, | |
| 315 | .bit_or_assign = .bin, | |
| 316 | .bit_xor_assign = .bin, | |
| 323 | .param_decl = .param_decl, | |
| 324 | .pow = .bin, | |
| 325 | .pow_assign = .bin, | |
| 326 | .pure = .un, | |
| 327 | .rem = .bin, | |
| 328 | .rem_assign = .bin, | |
| 329 | .root = .list, | |
| 330 | .shl = .bin, | |
| 317 | 331 | .shl_assign = .bin, |
| 332 | .shr = .bin, | |
| 318 | 333 | .shr_assign = .bin, |
| 319 | .bool_not = .un, | |
| 320 | .negate = .un, | |
| 321 | .bit_not = .un, | |
| 322 | .@"comptime" = .un, | |
| 323 | .pure = .un, | |
| 324 | .assert = .un, | |
| 325 | .label = .label, | |
| 334 | .string = .none, | |
| 335 | .struct_decl = .list, | |
| 336 | .struct_field = .un, | |
| 337 | .struct_init = .struct_init, | |
| 338 | .sub = .bin, | |
| 339 | .sub_assign = .bin, | |
| 340 | .var_decl = .var_decl, | |
| 341 | // sort-lines:end | |
| 326 | 342 | }); |
| 327 | 343 | |
| 328 | 344 | pub const Data = union { |
| 329 | 345 | none: struct { Unused = .zero, Unused = .zero }, |
| 330 | fn_decl: struct { | |
| 331 | // A `FnDecl` | |
| 346 | function: struct { | |
| 347 | /// A `Fn` | |
| 332 | 348 | extra: ExtraIndex, |
| 333 | 349 | body: NodeIndex, |
| 334 | 350 | }, |
| ... | ... | @@ -339,7 +355,7 @@ pub const Node = struct { |
| 339 | 355 | init: NodeIndex, |
| 340 | 356 | }, |
| 341 | 357 | param_decl: struct { |
| 342 | type: NodeIndex, | |
| 358 | type: OptionalNodeIndex, | |
| 343 | 359 | unused: Unused = .zero, |
| 344 | 360 | }, |
| 345 | 361 | bin: struct { |
| ... | ... | @@ -371,6 +387,13 @@ pub const Node = struct { |
| 371 | 387 | /// A `Call` |
| 372 | 388 | extra: ExtraIndex, |
| 373 | 389 | }, |
| 390 | struct_init: struct { | |
| 391 | /// .none => '.{ }' | |
| 392 | /// else => 'base{ ... }' | |
| 393 | base: OptionalNodeIndex, | |
| 394 | /// A `SubList` | |
| 395 | extra: ExtraIndex, | |
| 396 | }, | |
| 374 | 397 | @"if": struct { |
| 375 | 398 | cond: NodeIndex, |
| 376 | 399 | /// A `If` |
| ... | ... | @@ -457,12 +480,14 @@ pub const SubList = struct { |
| 457 | 480 | start: u32, |
| 458 | 481 | len: u32, |
| 459 | 482 | |
| 483 | pub const empty: SubList = .{ .len = 0, .start = 0 }; | |
| 484 | ||
| 460 | 485 | pub fn slice(self: SubList, ast: *const Ast) []const u32 { |
| 461 | 486 | return ast.extra[self.start..][0..self.len]; |
| 462 | 487 | } |
| 463 | 488 | }; |
| 464 | 489 | |
| 465 | pub const FnDecl = struct { | |
| 490 | pub const Fn = struct { | |
| 466 | 491 | params: SubList, |
| 467 | 492 | return_type: OptionalNodeIndex, |
| 468 | 493 | }; |
| ... | ... | @@ -489,6 +514,13 @@ pub const full = struct { |
| 489 | 514 | body: NodeIndex, |
| 490 | 515 | }; |
| 491 | 516 | |
| 517 | pub const FnExpr = struct { | |
| 518 | start_token: TokenIndex, | |
| 519 | params: []const NodeIndex, | |
| 520 | return_type: ?NodeIndex, | |
| 521 | body: NodeIndex, | |
| 522 | }; | |
| 523 | ||
| 492 | 524 | pub const VarDecl = struct { |
| 493 | 525 | start_token: TokenIndex, |
| 494 | 526 | public: bool, |
| ... | ... | @@ -498,11 +530,29 @@ pub const full = struct { |
| 498 | 530 | init: NodeIndex, |
| 499 | 531 | }; |
| 500 | 532 | |
| 501 | pub const ParamDecl = struct { | |
| 533 | pub const StructDecl = struct { | |
| 534 | start_token: TokenIndex, | |
| 535 | public: bool, | |
| 536 | id: []const u8, | |
| 537 | fields: []const NodeIndex, | |
| 538 | }; | |
| 539 | ||
| 540 | pub const StructField = struct { | |
| 502 | 541 | start_token: TokenIndex, |
| 503 | 542 | id: []const u8, |
| 504 | 543 | type: NodeIndex, |
| 505 | 544 | }; |
| 545 | ||
| 546 | pub const StructInit = struct { | |
| 547 | type: ?NodeIndex, | |
| 548 | fields: []const NodeIndex, | |
| 549 | }; | |
| 550 | ||
| 551 | pub const ParamDecl = struct { | |
| 552 | start_token: TokenIndex, | |
| 553 | id: []const u8, | |
| 554 | type: ?NodeIndex, | |
| 555 | }; | |
| 506 | 556 | }; |
| 507 | 557 | |
| 508 | 558 | pub fn deinit(ast: *Ast, gpa: std.mem.Allocator) void { |
| ... | ... | @@ -531,10 +581,32 @@ pub fn fullFnDecl(ast: Ast, node: NodeIndex) full.FnDecl { |
| 531 | 581 | const public = tok_tags[main_token -| 1] == .kw_pub; |
| 532 | 582 | assert(tok_tags[main_token] == .kw_fn); |
| 533 | 583 | assert(tok_tags[main_token + 1] == .id); |
| 584 | assert(tok_tags[main_token + 2] == .start_paren); | |
| 585 | const id = ast.tokenSlice(main_token + 1); | |
| 586 | ||
| 587 | const data = ast.nodes.items(.data)[node].function; | |
| 588 | const extra = ast.extraData(data.extra, Fn); | |
| 589 | ||
| 590 | return .{ | |
| 591 | .start_token = if (public) main_token - 1 else main_token, | |
| 592 | .public = public, | |
| 593 | .id = id, | |
| 594 | .body = data.body, | |
| 595 | .params = extra.params.slice(&ast), | |
| 596 | .return_type = extra.return_type.unwrap(), | |
| 597 | }; | |
| 598 | } | |
| 599 | ||
| 600 | pub fn fullFnExpr(ast: Ast, node: NodeIndex) full.FnDecl { | |
| 601 | const tok_tags = ast.tokens.items(.tag); | |
| 602 | ||
| 603 | const main_token = ast.nodes.items(.main_token)[node]; | |
| 604 | const public = tok_tags[main_token -| 1] == .kw_pub; | |
| 605 | assert(tok_tags[main_token] == .kw_fn); | |
| 534 | 606 | const id = ast.tokenSlice(main_token + 1); |
| 535 | 607 | |
| 536 | const data = ast.nodes.items(.data)[node].fn_decl; | |
| 537 | const extra = ast.extraData(data.extra, FnDecl); | |
| 608 | const data = ast.nodes.items(.data)[node].function; | |
| 609 | const extra = ast.extraData(data.extra, Fn); | |
| 538 | 610 | |
| 539 | 611 | return .{ |
| 540 | 612 | .start_token = if (public) main_token - 1 else main_token, |
| ... | ... | @@ -546,6 +618,25 @@ pub fn fullFnDecl(ast: Ast, node: NodeIndex) full.FnDecl { |
| 546 | 618 | }; |
| 547 | 619 | } |
| 548 | 620 | |
| 621 | pub fn fullStructDecl(ast: Ast, node: NodeIndex) full.StructDecl { | |
| 622 | const tok_tags = ast.tokens.items(.tag); | |
| 623 | ||
| 624 | const main_token = ast.nodes.items(.main_token)[node]; | |
| 625 | const public = tok_tags[main_token -| 1] == .kw_pub; | |
| 626 | assert(tok_tags[main_token] == .kw_struct); | |
| 627 | assert(tok_tags[main_token + 1] == .id); | |
| 628 | const id = ast.tokenSlice(main_token + 1); | |
| 629 | ||
| 630 | const data = ast.nodes.items(.data)[node].list; | |
| 631 | ||
| 632 | return .{ | |
| 633 | .start_token = if (public) main_token - 1 else main_token, | |
| 634 | .public = public, | |
| 635 | .id = id, | |
| 636 | .fields = data.slice(&ast), | |
| 637 | }; | |
| 638 | } | |
| 639 | ||
| 549 | 640 | pub fn fullVarDecl(ast: Ast, node: NodeIndex) full.VarDecl { |
| 550 | 641 | const tok_tags = ast.tokens.items(.tag); |
| 551 | 642 | |
| ... | ... | @@ -584,7 +675,30 @@ pub fn fullParamDecl(ast: Ast, node: NodeIndex) full.ParamDecl { |
| 584 | 675 | return .{ |
| 585 | 676 | .start_token = main_token, |
| 586 | 677 | .id = id, |
| 587 | .type = data.type, | |
| 678 | .type = data.type.unwrap(), | |
| 679 | }; | |
| 680 | } | |
| 681 | ||
| 682 | pub fn fullStructField(ast: Ast, node: NodeIndex) full.StructField { | |
| 683 | const tok_tags = ast.tokens.items(.tag); | |
| 684 | ||
| 685 | const main_token = ast.nodes.items(.main_token)[node]; | |
| 686 | assert(tok_tags[main_token] == .id); | |
| 687 | const id = ast.tokenSlice(main_token); | |
| 688 | const data = ast.nodes.items(.data)[node].un; | |
| 689 | return .{ | |
| 690 | .start_token = main_token, | |
| 691 | .id = id, | |
| 692 | .type = data.expr, | |
| 693 | }; | |
| 694 | } | |
| 695 | ||
| 696 | pub fn fullStructInit(ast: Ast, node: NodeIndex) full.StructInit { | |
| 697 | assert(ast.nodeTag(node) == .struct_init); | |
| 698 | const data = ast.nodeData(node).struct_init; | |
| 699 | return .{ | |
| 700 | .type = data.base.unwrap(), | |
| 701 | .fields = ast.extraData(data.extra, SubList).slice(&ast), | |
| 588 | 702 | }; |
| 589 | 703 | } |
| 590 | 704 |
src/core/Comp.zig+156-95| ... | ... | @@ -1,4 +1,7 @@ |
| 1 | //! Compilation | |
| 1 | //! Language Compilation Unit | |
| 2 | //! | |
| 3 | //! This is all of the state for a compilation, which starts at one or more URLs | |
| 4 | //! (entry points), and will eventually emit one or more object files. | |
| 2 | 5 | |
| 3 | 6 | /// General purpose allocator |
| 4 | 7 | gpa: Allocator, |
| ... | ... | @@ -7,13 +10,9 @@ intern_pool: InternPool = .{}, |
| 7 | 10 | /// All files in the compilation |
| 8 | 11 | files: std.StringArrayHashMapUnmanaged(*File) = .{}, |
| 9 | 12 | |
| 10 | /// # of sema instructions in this compilation cycle | |
| 11 | analyzed_instructions: usize = 0, | |
| 12 | ||
| 13 | 13 | options: Options = .{}, |
| 14 | 14 | |
| 15 | // TODO: consider computing this dynamically per-link | |
| 16 | current_compile_decls: std.ArrayListUnmanaged(Decl.Index) = .{}, | |
| 15 | backend: Backend, | |
| 17 | 16 | |
| 18 | 17 | pub fn init(comp: *Comp) !void { |
| 19 | 18 | try comp.intern_pool.init(comp.gpa); |
| ... | ... | @@ -25,9 +24,47 @@ pub fn deinit(comp: *Comp) void { |
| 25 | 24 | file.deinit(comp.gpa); |
| 26 | 25 | } |
| 27 | 26 | comp.files.deinit(comp.gpa); |
| 28 | comp.current_compile_decls.deinit(comp.gpa); | |
| 27 | comp.backend.deinit(comp); | |
| 28 | } | |
| 29 | ||
| 30 | /// This is the entry point for compilation. | |
| 31 | /// Returns the Decl of the top-level code. | |
| 32 | pub fn semaEntryPoint(comp: *Comp, file: *File) !Decl.Index { | |
| 33 | var report: Report = undefined; | |
| 34 | const teg = try file.getTeg(comp, &report); | |
| 35 | ||
| 36 | // TODO, move this elsewhere. we should print all errors and warnings at once | |
| 37 | if (teg.report.warnings.len > 0) { | |
| 38 | try teg.report.dumpToStdErr(comp); | |
| 39 | } | |
| 40 | ||
| 41 | const ns = try comp.indexTegModule(file, .main_module_inst); | |
| 42 | const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, .empty) orelse | |
| 43 | unreachable; // Transform should always emit a top-level function | |
| 44 | const decl = comp.intern_pool.declPtr(decl_index); | |
| 45 | ||
| 46 | var arena = std.heap.ArenaAllocator.init(comp.gpa); | |
| 47 | defer arena.deinit(); | |
| 48 | ||
| 49 | var sema: Sema = .{ | |
| 50 | .comp = comp, | |
| 51 | .gpa = comp.gpa, | |
| 52 | .code = teg, | |
| 53 | .arena = arena.allocator(), | |
| 54 | .ip = &comp.intern_pool, | |
| 55 | .decl = decl, | |
| 56 | .decl_index = decl_index, | |
| 57 | .namespace_index = ns, | |
| 58 | }; | |
| 59 | try sema.init(); | |
| 60 | defer sema.deinit(); | |
| 61 | _ = try sema.analyzeDeclaration(); | |
| 62 | try sema.finalize(); | |
| 63 | ||
| 64 | return decl_index; | |
| 29 | 65 | } |
| 30 | 66 | |
| 67 | /// Add or retrieve a file to the compilation via it's file path. | |
| 31 | 68 | pub fn getFile(comp: *Comp, absolute_file_path: []const u8) !*File { |
| 32 | 69 | assert(std.fs.path.isAbsolute(absolute_file_path)); |
| 33 | 70 | const gop = try comp.files.getOrPut(comp.gpa, absolute_file_path); |
| ... | ... | @@ -38,8 +75,8 @@ pub fn getFile(comp: *Comp, absolute_file_path: []const u8) !*File { |
| 38 | 75 | file.* = .{ |
| 39 | 76 | .file_path = absolute_file_path, |
| 40 | 77 | .path_digest = null, |
| 41 | .root_module = .none, | |
| 42 | .status = .never_loader, | |
| 78 | .root_namespace = .none, | |
| 79 | .status = .never_loaded, | |
| 43 | 80 | .source = null, |
| 44 | 81 | .ast = null, |
| 45 | 82 | .teg = null, |
| ... | ... | @@ -53,13 +90,15 @@ pub const Source = struct { |
| 53 | 90 | stat: Stat, |
| 54 | 91 | }; |
| 55 | 92 | |
| 93 | /// A file in the compilation and it's associated data structures. Ast and Teg | |
| 94 | /// are not guaranteed to be loaded, even if it's `status` says `success`. | |
| 56 | 95 | pub const File = struct { |
| 57 | 96 | /// TODO: change this to a URL |
| 58 | 97 | file_path: []const u8, |
| 59 | 98 | |
| 60 | root_module: Decl.Index.Optional, | |
| 99 | root_namespace: Decl.Index.Optional, | |
| 61 | 100 | status: enum { |
| 62 | never_loader, | |
| 101 | never_loaded, | |
| 63 | 102 | /// A failure occurred while loading the file. Retrying might succeed. |
| 64 | 103 | retryable_failure, |
| 65 | 104 | /// Errors during parse |
| ... | ... | @@ -110,7 +149,7 @@ pub const File = struct { |
| 110 | 149 | // used for error reporting. We need to keep the stat fields stale so that |
| 111 | 150 | // transformFile can know to regenerate Teg. |
| 112 | 151 | |
| 113 | const loaded_source = Source{ | |
| 152 | const loaded_source: Source = .{ | |
| 114 | 153 | .code = source, |
| 115 | 154 | .stat = .{ |
| 116 | 155 | .size = stat.size, |
| ... | ... | @@ -212,24 +251,34 @@ pub const File = struct { |
| 212 | 251 | } |
| 213 | 252 | }; |
| 214 | 253 | |
| 254 | /// A declaration is a named value which also represents the smallest unit of | |
| 255 | /// compilation/re-compilation. A declaration contains zero or more `Func` | |
| 256 | /// objects, which represent the different callable values the declaration has. | |
| 257 | /// | |
| 258 | /// A special type of declaration is the top-level declaration, which has the | |
| 259 | /// name "" (empty string). This is used to facilitate top-level side effects. | |
| 215 | 260 | pub const Decl = struct { |
| 216 | 261 | name: InternPool.NullTerminatedString, |
| 217 | val: ?InternPool.Value, | |
| 262 | ||
| 263 | /// The Value that this declaration evaluates to. | |
| 264 | raw_value: ?InternPool.Value, | |
| 265 | ||
| 218 | 266 | /// The direct parent namespace of the Decl. In the case of the Decl |
| 219 | 267 | /// corresponding to a file, this is the namespace of the struct, since |
| 220 | 268 | /// there is no parent. |
| 221 | 269 | src_namespace: Namespace.Index, |
| 222 | 270 | |
| 223 | /// The AST node index of this declaration. | |
| 224 | /// Must be recomputed when the corresponding source file is modified. | |
| 271 | // /// The AST node index of this declaration. | |
| 272 | // /// Must be recomputed when the corresponding source file is modified. | |
| 225 | 273 | // src_node: Ast.NodeIndex, |
| 226 | /// Line number corresponding to `src_node`. Stored separately so that source files | |
| 227 | /// do not need to be loaded into memory in order to compute debug line numbers. | |
| 228 | /// This value is absolute. | |
| 274 | ||
| 275 | // /// Line number corresponding to `src_node`. Stored separately so that source files | |
| 276 | // /// do not need to be loaded into memory in order to compute debug line numbers. | |
| 277 | // /// This value is absolute. | |
| 229 | 278 | // src_line: u32, |
| 230 | /// Index of the ZIR `declaration` instruction from which this `Decl` was created. | |
| 231 | /// For the root `Decl` of a `File` and legacy anonymous decls, this is `.none`. | |
| 232 | teg_decl_index: InternPool.TrackedInst.Index.Optional, | |
| 279 | ||
| 280 | /// Index of the Teg `declaration` instruction from which this `Decl` was created. | |
| 281 | teg_decl_index: InternPool.TrackedInst.Index, | |
| 233 | 282 | |
| 234 | 283 | /// Represents the "shallow" analysis status. For example, for decls that are functions, |
| 235 | 284 | /// the function type is analyzed with this set to `in_progress`; however, the semantic |
| ... | ... | @@ -242,20 +291,21 @@ pub const Decl = struct { |
| 242 | 291 | /// Semantic analysis for this Decl is running right now. |
| 243 | 292 | /// This state detects dependency loops. |
| 244 | 293 | in_progress, |
| 245 | /// Semantic analysis for this Decl is running right now | |
| 246 | /// But .value is valid | |
| 247 | in_progress_prototype_available, | |
| 294 | /// Semantic analysis for this Decl is running right now, and has | |
| 295 | /// completed enough to reify the type of the result, but not the result | |
| 296 | /// itself. | |
| 297 | in_progress_type_available, | |
| 248 | 298 | /// The file corresponding to this Decl had a parse error or ZIR error. |
| 249 | 299 | /// There will be a corresponding ErrorMsg in Zcu.failed_files. |
| 250 | file_failure, | |
| 300 | // file_failure, | |
| 251 | 301 | /// This Decl might be OK but it depends on another one which did not |
| 252 | 302 | /// successfully complete semantic analysis. |
| 253 | dependency_failure, | |
| 303 | // dependency_failure, | |
| 254 | 304 | /// Semantic analysis failure. |
| 255 | 305 | /// There will be a corresponding ErrorMsg in Zcu.failed_decls. |
| 256 | sema_failure, | |
| 306 | // sema_failure, | |
| 257 | 307 | /// There will be a corresponding ErrorMsg in Zcu.failed_decls. |
| 258 | codegen_failure, | |
| 308 | // codegen_failure, | |
| 259 | 309 | /// Semantic analysis and constant value codegen of this Decl has |
| 260 | 310 | /// succeeded. However, the Decl may be outdated due to an in-progress |
| 261 | 311 | /// update. Note that for a function, this does not mean codegen of the |
| ... | ... | @@ -264,25 +314,35 @@ pub const Decl = struct { |
| 264 | 314 | complete, |
| 265 | 315 | }, |
| 266 | 316 | |
| 267 | codegen: ?JsTree = null, | |
| 268 | ||
| 269 | is_pub: bool, | |
| 317 | pub fn value(decl: *const Decl) InternPool.Value { | |
| 318 | assert(decl.analysis == .complete); | |
| 319 | return decl.raw_value orelse unreachable; | |
| 320 | } | |
| 270 | 321 | |
| 271 | /// What kind of a declaration this is. | |
| 272 | kind: Kind, | |
| 322 | pub fn @"type"(decl: *const Decl, ip: *const InternPool) InternPool.Type { | |
| 323 | return switch (decl.analysis) { | |
| 324 | .unreferenced => unreachable, | |
| 325 | .in_progress => unreachable, | |
| 326 | .in_progress_prototype_available => decl.raw_value.?.toTypeUnsafe(ip), | |
| 327 | .complete => decl.raw_value.?.typeOf(ip), | |
| 328 | }; | |
| 329 | } | |
| 273 | 330 | |
| 274 | 331 | pub fn deinit(decl: *Decl, gpa: Allocator) void { |
| 332 | _ = gpa; // autofix | |
| 275 | 333 | defer decl.* = undefined; |
| 276 | if (decl.codegen) |*cg| cg.deinit(gpa); | |
| 334 | // if (decl.codegen) |*cg| cg.deinit(gpa); | |
| 277 | 335 | } |
| 278 | 336 | |
| 279 | pub const Kind = enum { | |
| 280 | named, | |
| 281 | }; | |
| 282 | ||
| 283 | 337 | pub const Index = InternPool.DeclIndex; |
| 284 | 338 | }; |
| 285 | 339 | |
| 340 | pub const Func = struct {}; | |
| 341 | ||
| 342 | /// A namespace is a container for declarations. Currently the only form of | |
| 343 | /// namespace that exists is file-scope. In the future, the `module` keyword | |
| 344 | /// will create nested namespaces, and it is probable that `struct` and other | |
| 345 | /// constructs will also create namespaces. | |
| 286 | 346 | pub const Namespace = struct { |
| 287 | 347 | parent: Index.Optional, |
| 288 | 348 | file_scope: *File, |
| ... | ... | @@ -300,7 +360,12 @@ pub const Namespace = struct { |
| 300 | 360 | return std.hash.uint32(@intFromEnum(decl.name)); |
| 301 | 361 | } |
| 302 | 362 | |
| 303 | pub fn eql(ctx: @This(), a_decl_index: Decl.Index, b_decl_index: Decl.Index, b_index: usize) bool { | |
| 363 | pub fn eql( | |
| 364 | ctx: @This(), | |
| 365 | a_decl_index: Decl.Index, | |
| 366 | b_decl_index: Decl.Index, | |
| 367 | b_index: usize, | |
| 368 | ) bool { | |
| 304 | 369 | _ = b_index; |
| 305 | 370 | const a_decl = ctx.comp.intern_pool.declPtr(a_decl_index); |
| 306 | 371 | const b_decl = ctx.comp.intern_pool.declPtr(b_decl_index); |
| ... | ... | @@ -316,22 +381,30 @@ pub const Namespace = struct { |
| 316 | 381 | return std.hash.uint32(@intFromEnum(s)); |
| 317 | 382 | } |
| 318 | 383 | |
| 319 | pub fn eql(self: @This(), a: InternPool.NullTerminatedString, b_decl_index: Decl.Index, b_index: usize) bool { | |
| 384 | pub fn eql( | |
| 385 | self: @This(), | |
| 386 | a: InternPool.NullTerminatedString, | |
| 387 | b_decl_index: Decl.Index, | |
| 388 | b_index: usize, | |
| 389 | ) bool { | |
| 320 | 390 | _ = b_index; |
| 321 | 391 | return a == self.comp.intern_pool.declPtr(b_decl_index).name; |
| 322 | 392 | } |
| 323 | 393 | }; |
| 324 | 394 | |
| 325 | pub fn lookupDirect(ns: *Namespace, comp: *Comp, name: InternPool.NullTerminatedString) ?Decl.Index { | |
| 395 | pub fn lookupDirect( | |
| 396 | ns: *Namespace, | |
| 397 | comp: *Comp, | |
| 398 | name: InternPool.NullTerminatedString, | |
| 399 | ) ?Decl.Index { | |
| 326 | 400 | return ns.decls.getKeyAdapted(name, Namespace.DeclNameAdapter{ .comp = comp }); |
| 327 | 401 | } |
| 328 | 402 | |
| 329 | 403 | pub const Index = InternPool.NamespaceIndex; |
| 330 | 404 | }; |
| 331 | 405 | |
| 332 | /// Index a module in a Teg. | |
| 333 | /// When a File's Teg is loaded, we call this on the root module. | |
| 334 | /// submodules are analyzed lazily. | |
| 406 | /// Index a Teg's module scope as a namespace. This prepares `Decl` objects for | |
| 407 | /// every declaration, but does not begin analysis of them. | |
| 335 | 408 | pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !Namespace.Index { |
| 336 | 409 | const teg = file.teg.?; |
| 337 | 410 | |
| ... | ... | @@ -351,12 +424,10 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N |
| 351 | 424 | const tracked_inst = try comp.intern_pool.trackInst(comp.gpa, file, decl_index); |
| 352 | 425 | const decl = try comp.intern_pool.createDecl(comp.gpa, .{ |
| 353 | 426 | .name = name, |
| 354 | .val = null, | |
| 427 | .raw_value = null, | |
| 355 | 428 | .src_namespace = ns, |
| 356 | .teg_decl_index = tracked_inst.toOptional(), | |
| 429 | .teg_decl_index = tracked_inst, | |
| 357 | 430 | .analysis = .unreferenced, |
| 358 | .is_pub = false, // todo: | |
| 359 | .kind = .named, | |
| 360 | 431 | }); |
| 361 | 432 | try ns_ptr.decls.putNoClobberContext(comp.gpa, decl, void{}, .{ .comp = comp }); |
| 362 | 433 | } |
| ... | ... | @@ -364,42 +435,7 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N |
| 364 | 435 | return ns; |
| 365 | 436 | } |
| 366 | 437 | |
| 367 | pub fn semaEntryPoint(comp: *Comp, file: *File) !void { | |
| 368 | var report: Report = undefined; | |
| 369 | const teg = try file.getTeg(comp, &report); | |
| 370 | ||
| 371 | // TODO, move this elsewhere. we should print all errors and warnings at once | |
| 372 | if (teg.report.warnings.len > 0) { | |
| 373 | try teg.report.dumpToStdErr(comp); | |
| 374 | } | |
| 375 | ||
| 376 | const ns = try comp.indexTegModule(file, .main_module_inst); | |
| 377 | const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, .empty) orelse | |
| 378 | @panic("no top level code?"); | |
| 379 | const decl = comp.intern_pool.declPtr(decl_index); | |
| 380 | ||
| 381 | assert(comp.current_compile_decls.items.len == 0); | |
| 382 | ||
| 383 | var arena = std.heap.ArenaAllocator.init(comp.gpa); | |
| 384 | defer arena.deinit(); | |
| 385 | ||
| 386 | var sema: Sema = .{ | |
| 387 | .comp = comp, | |
| 388 | .gpa = comp.gpa, | |
| 389 | .code = teg, | |
| 390 | .arena = arena.allocator(), | |
| 391 | .ip = &comp.intern_pool, | |
| 392 | .decl = decl, | |
| 393 | .decl_index = decl_index, | |
| 394 | .namespace_index = ns, | |
| 395 | }; | |
| 396 | try sema.init(); | |
| 397 | defer sema.deinit(); | |
| 398 | _ = try sema.analyzeDeclaration(); | |
| 399 | try sema.finalize(); | |
| 400 | } | |
| 401 | ||
| 402 | pub fn codeGenDecl(c: *Comp, decl: *Decl, air: *const Air) !void { | |
| 438 | pub fn codeGenDecl(c: *Comp, value: InternPool.Value, air: *const Air) !void { | |
| 403 | 439 | const t = tracer.trace(@src()); |
| 404 | 440 | defer t.end(); |
| 405 | 441 | |
| ... | ... | @@ -407,20 +443,48 @@ pub fn codeGenDecl(c: *Comp, decl: *Decl, air: *const Air) !void { |
| 407 | 443 | defer arena_state.deinit(); |
| 408 | 444 | const arena = arena_state.allocator(); |
| 409 | 445 | |
| 410 | var cg = try JsNodeGen.init(c.gpa, arena, &c.intern_pool, air); | |
| 411 | errdefer cg.deinit(); | |
| 412 | try cg.build(); | |
| 413 | decl.codegen = try cg.finalize(); | |
| 414 | } | |
| 415 | ||
| 416 | pub fn link(c: *Comp, writer: anytype) !void { | |
| 417 | try JsLink.linkJsCode(c, c.current_compile_decls.items, writer); | |
| 446 | _ = try c.backend.handleFunctionAir(c, value, air, arena); | |
| 418 | 447 | } |
| 419 | 448 | |
| 420 | 449 | const Options = struct { |
| 421 | 450 | dump_ir: bool = false, |
| 422 | 451 | }; |
| 423 | 452 | |
| 453 | pub const Backend = union(enum) { | |
| 454 | js: *@import("js/Js.zig"), | |
| 455 | ||
| 456 | pub fn deinit(backend: Backend, comp: *Comp) void { | |
| 457 | backend.dispatch("deinit", .{comp}); | |
| 458 | } | |
| 459 | ||
| 460 | pub const HandleFunctionAirError = error{OutOfMemory}; | |
| 461 | ||
| 462 | /// Once semantic analysis finishes analyzing a Function value, it hands | |
| 463 | /// `Air` to the backend for code generation. Returned is an opaque integer | |
| 464 | /// which is stored on the Function value. | |
| 465 | pub fn handleFunctionAir( | |
| 466 | backend: Backend, | |
| 467 | comp: *Comp, | |
| 468 | value: InternPool.Value, | |
| 469 | air: *const Air, | |
| 470 | arena: Allocator, | |
| 471 | ) HandleFunctionAirError!u32 { | |
| 472 | return backend.dispatch("handleFunctionAir", .{ comp, value, air, arena }); | |
| 473 | } | |
| 474 | ||
| 475 | inline fn dispatch( | |
| 476 | backend: Backend, | |
| 477 | comptime name: []const u8, | |
| 478 | args: anytype, | |
| 479 | ) @typeInfo(@TypeOf(@field(Backend, name))).Fn.return_type.? { | |
| 480 | return switch (backend) { | |
| 481 | inline else => |ptr| @call(.auto, @field(@TypeOf(ptr.*), name), .{ptr} ++ args), | |
| 482 | }; | |
| 483 | } | |
| 484 | ||
| 485 | pub const Tag = std.meta.FieldEnum(Backend); | |
| 486 | }; | |
| 487 | ||
| 424 | 488 | const Comp = @This(); |
| 425 | 489 | |
| 426 | 490 | const std = @import("std"); |
| ... | ... | @@ -437,9 +501,6 @@ const Teg = @import("Teg.zig"); |
| 437 | 501 | const Cache = @import("Cache.zig"); |
| 438 | 502 | const Sema = @import("Sema.zig"); |
| 439 | 503 | const Transform = @import("Transform.zig"); |
| 440 | const JsNodeGen = @import("JsNodeGen.zig"); | |
| 441 | const JsTree = @import("JsTree.zig"); | |
| 442 | const JsLink = @import("JsLink.zig"); | |
| 443 | 504 | const Report = @import("Report.zig"); |
| 444 | 505 | const Parse = @import("Parse.zig"); |
| 445 | 506 |
src/core/InternPool.zig+241-102| ... | ... | @@ -1,8 +1,21 @@ |
| 1 | 1 | //! Large parts of this file are based on the Zig compiler's InternPool. |
| 2 | 2 | //! https://github.com/ziglang/zig/blob/0.12.0/src/InternPool.zig |
| 3 | ||
| 4 | /// Maps `Key` to `Index`. `Key` objects are not stored anywhere; they are | |
| 5 | /// constructed lazily. | |
| 3 | //! | |
| 4 | //! All interned objects have both a Value and a Type. Type is a subset of | |
| 5 | //! Value, since the language allows operating on types the same as values. | |
| 6 | //! This data structure is intended to be serializable for incremental | |
| 7 | //! compilation efforts. | |
| 8 | //! | |
| 9 | //! This data structure is self-contained, with the following exceptions: | |
| 10 | //! * Comp.Namespace has a pointer to Comp.File | |
| 11 | ||
| 12 | /// Maps `Key` to `Value`. `Key` objects are not stored anywhere; they are | |
| 13 | /// constructed lazily. The `Value` is also stored out of line in `.items`. The | |
| 14 | /// ArrayHashMap functions as a list of hashes to allow fast lookups, and then | |
| 15 | /// reading it's data out of `.items` and `.extra` via the index lookup. | |
| 16 | /// | |
| 17 | /// The preferred way to access these this data through `.get()` and the methods | |
| 18 | /// on `Value` and `Type`. | |
| 6 | 19 | map: std.AutoArrayHashMapUnmanaged(void, void) = .{}, |
| 7 | 20 | items: std.MultiArrayList(Item) = .{}, |
| 8 | 21 | extra: ExtraStore = .{}, |
| ... | ... | @@ -45,29 +58,6 @@ allocated_namespaces: std.SegmentedList(Comp.Namespace, 0) = .{}, |
| 45 | 58 | /// Same pattern as with `decls_free_list`. |
| 46 | 59 | namespaces_free_list: std.ArrayListUnmanaged(NamespaceIndex) = .{}, |
| 47 | 60 | |
| 48 | /// How many items in the InternPool are statically known. | |
| 49 | /// This is specified with an integer literal and a corresponding comptime | |
| 50 | /// assert below to break an unfortunate and arguably incorrect dependency loop | |
| 51 | /// when compiling. | |
| 52 | pub const static_len = Teg.Inst.Index.static_len; | |
| 53 | comptime { | |
| 54 | // @compileLog(static_len, static_keys.len); | |
| 55 | assert(static_len == static_keys.len); | |
| 56 | } | |
| 57 | ||
| 58 | pub const Item = struct { | |
| 59 | tag: Tag, | |
| 60 | data: EncodedData, | |
| 61 | ||
| 62 | pub fn encode(comptime tag: Tag, data: tag.Data()) Item { | |
| 63 | return .{ .tag = tag, .data = EncodedData.encode(tag, data) }; | |
| 64 | } | |
| 65 | }; | |
| 66 | ||
| 67 | pub const ExtraStore = dod.ExtraStore(.intern_pool, .@"var"); | |
| 68 | pub const EncodedData = dod.EncodedData(Tag, u32); | |
| 69 | pub const ExtraIndex = ExtraStore.Index; | |
| 70 | ||
| 71 | 61 | pub const Tag = enum(u8) { |
| 72 | 62 | /// This special tag represents a value that was removed from this pool via |
| 73 | 63 | /// `InternPool.remove`. The item remains allocated to preserve indices, but |
| ... | ... | @@ -76,10 +66,10 @@ pub const Tag = enum(u8) { |
| 76 | 66 | removed, |
| 77 | 67 | |
| 78 | 68 | /// A function type |
| 79 | /// data is an extra index into a `FuncType` | |
| 80 | type_function, | |
| 69 | function_type, | |
| 70 | /// A struct type | |
| 71 | struct_type, | |
| 81 | 72 | /// A type that can be represented with only an enum tag. |
| 82 | /// data is SimpleType enum value. | |
| 83 | 73 | simple_type, |
| 84 | 74 | |
| 85 | 75 | // --- before this are types --- |
| ... | ... | @@ -90,21 +80,18 @@ pub const Tag = enum(u8) { |
| 90 | 80 | simple_value, |
| 91 | 81 | /// A numeric literal that can be represented as a float. |
| 92 | 82 | /// Integers greater than 2^53 known at compile time are currently not supported. |
| 93 | /// data is an extra index into a `Float` | |
| 94 | 83 | comptime_float, |
| 95 | 84 | /// An int32 value |
| 96 | /// data is i32 | |
| 97 | 85 | int32, |
| 98 | 86 | /// A float value |
| 99 | /// data is an extra index into a `Float` | |
| 100 | 87 | float, |
| 101 | 88 | /// A function |
| 102 | /// data is an extra index into a `FuncDecl` | |
| 103 | 89 | func, |
| 104 | 90 | /// A string |
| 105 | /// data is an extra index into a `String` | |
| 106 | 91 | string, |
| 107 | 92 | |
| 93 | struct_value, | |
| 94 | ||
| 108 | 95 | pub fn Data(comptime tag: Tag) type { |
| 109 | 96 | return switch (tag) { |
| 110 | 97 | .removed, |
| ... | ... | @@ -128,8 +115,10 @@ pub const Tag = enum(u8) { |
| 128 | 115 | => unreachable, // does not have a payload |
| 129 | 116 | .float, .comptime_float => Float, |
| 130 | 117 | .func => FuncDecl, |
| 131 | .type_function => TypeFunction, | |
| 118 | .function_type => TypeFunction, | |
| 132 | 119 | .string => String.WithLen, |
| 120 | .struct_value => StructValue, | |
| 121 | .struct_type => TypeStruct, | |
| 133 | 122 | }; |
| 134 | 123 | } |
| 135 | 124 | |
| ... | ... | @@ -147,8 +136,23 @@ pub const Tag = enum(u8) { |
| 147 | 136 | params_len: u32, |
| 148 | 137 | return_type: Type, |
| 149 | 138 | }; |
| 139 | ||
| 140 | /// Trailing: | |
| 141 | /// 0. field_key: NullTerminatedString. Repeated per field_len | |
| 142 | /// 1. field_type: Type. Repeated per field_len | |
| 143 | pub const TypeStruct = struct { | |
| 144 | fields_len: u32, | |
| 145 | }; | |
| 146 | ||
| 147 | /// Trailing: | |
| 148 | /// 0. value: Value. Repeated ty.structInfo().field_len | |
| 149 | pub const StructValue = struct { | |
| 150 | /// The type of this field | |
| 151 | ty: Type, | |
| 152 | }; | |
| 150 | 153 | }; |
| 151 | 154 | |
| 155 | /// This is an index into .items | |
| 152 | 156 | pub const Value = enum(u32) { |
| 153 | 157 | pub const first_type: Value = .opaque_type; |
| 154 | 158 | pub const last_type: Value = .noreturn_type; |
| ... | ... | @@ -176,25 +180,26 @@ pub const Value = enum(u32) { |
| 176 | 180 | none = std.math.maxInt(u32), |
| 177 | 181 | _, |
| 178 | 182 | |
| 179 | /// An array of `Index` existing within the `extra` array. | |
| 183 | /// An array of `Value` existing within the `extra` array. | |
| 180 | 184 | /// This type exists to provide a struct with lifetime that is |
| 181 | 185 | /// not invalidated when items are added to the `InternPool`. |
| 186 | /// TODO: Replace with ExtraStore.Slice(Value) | |
| 182 | 187 | pub const Slice = struct { |
| 183 | 188 | start: u32, |
| 184 | 189 | len: u32, |
| 185 | 190 | |
| 186 | pub fn get(slice: Slice, ip: *const InternPool) []Value { | |
| 191 | pub fn get(slice: Slice, ip: *const InternPool) []const Value { | |
| 187 | 192 | return @ptrCast(ip.extra.list.items[slice.start..][0..slice.len]); |
| 188 | 193 | } |
| 189 | 194 | }; |
| 190 | 195 | |
| 191 | /// Used for a map of `Index` values to the index within a list of `Index` values. | |
| 196 | /// Used for a map of `Value` values to the index within a list of `Value` values. | |
| 192 | 197 | const Adapter = struct { |
| 193 | indexes: []const Value, | |
| 198 | values: []const Value, | |
| 194 | 199 | |
| 195 | 200 | pub fn eql(ctx: @This(), a: Value, b_void: void, b_map_index: usize) bool { |
| 196 | 201 | _ = b_void; |
| 197 | return a == ctx.indexes[b_map_index]; | |
| 202 | return a == ctx.values[b_map_index]; | |
| 198 | 203 | } |
| 199 | 204 | |
| 200 | 205 | pub fn hash(ctx: @This(), a: Value) u32 { |
| ... | ... | @@ -216,7 +221,7 @@ pub const Value = enum(u32) { |
| 216 | 221 | } |
| 217 | 222 | |
| 218 | 223 | pub fn toKey(value: Value, ip: *const InternPool) Key { |
| 219 | return ip.indexToKey(value); | |
| 224 | return ip.valueToKey(value); | |
| 220 | 225 | } |
| 221 | 226 | |
| 222 | 227 | pub fn dump(value: Value, ip: *const InternPool) std.fmt.Formatter(Value.formatValue) { |
| ... | ... | @@ -240,8 +245,9 @@ pub const Value = enum(u32) { |
| 240 | 245 | return switch (value.valueTag(ip)) { |
| 241 | 246 | .removed => unreachable, // UAF |
| 242 | 247 | |
| 243 | .type_function => .type, | |
| 248 | .function_type => .type, | |
| 244 | 249 | .simple_type => .type, |
| 250 | .struct_type => .type, | |
| 245 | 251 | |
| 246 | 252 | .simple_value => switch (value.valueData(ip, .simple_value)) { |
| 247 | 253 | .true => .bool, |
| ... | ... | @@ -254,6 +260,7 @@ pub const Value = enum(u32) { |
| 254 | 260 | .float => .float, |
| 255 | 261 | .string => .string, |
| 256 | 262 | .func => value.toKey(ip).func.ty, |
| 263 | .struct_value => value.toKey(ip).struct_value.ty, | |
| 257 | 264 | }; |
| 258 | 265 | } |
| 259 | 266 | |
| ... | ... | @@ -263,7 +270,8 @@ pub const Value = enum(u32) { |
| 263 | 270 | return switch (value.valueTag(ip)) { |
| 264 | 271 | .removed => unreachable, // UAF |
| 265 | 272 | |
| 266 | .type_function, | |
| 273 | .function_type, | |
| 274 | .struct_type, | |
| 267 | 275 | .simple_type, |
| 268 | 276 | => @enumFromInt(@intFromEnum(value)), |
| 269 | 277 | |
| ... | ... | @@ -276,7 +284,8 @@ pub const Value = enum(u32) { |
| 276 | 284 | |
| 277 | 285 | pub fn toTypeUnsafe(value: Value, proof_ip: *const InternPool) Type { |
| 278 | 286 | if (std.debug.runtime_safety) { |
| 279 | return value.toType(proof_ip).?; | |
| 287 | return value.toType(proof_ip) orelse | |
| 288 | std.debug.panic("{} is not a type", .{value.dump(proof_ip)}); | |
| 280 | 289 | } |
| 281 | 290 | return @enumFromInt(@intFromEnum(value)); |
| 282 | 291 | } |
| ... | ... | @@ -301,6 +310,8 @@ pub const Value = enum(u32) { |
| 301 | 310 | } |
| 302 | 311 | }; |
| 303 | 312 | |
| 313 | /// Type is subset of Value which represents the values that have typeOf() == Value.type_type | |
| 314 | /// It is a bitcast, so going to and from are only the cost of ensuring the Value is a Type | |
| 304 | 315 | pub const Type = enum(u32) { |
| 305 | 316 | @"opaque" = @intFromEnum(Value.opaque_type), |
| 306 | 317 | bool = @intFromEnum(Value.bool_type), |
| ... | ... | @@ -315,7 +326,8 @@ pub const Type = enum(u32) { |
| 315 | 326 | |
| 316 | 327 | // Restricted version of `InternPool.Tag` |
| 317 | 328 | pub const Tag = enum(u32) { |
| 318 | type_function = @intFromEnum(InternPool.Tag.type_function), | |
| 329 | function_type = @intFromEnum(InternPool.Tag.function_type), | |
| 330 | struct_type = @intFromEnum(InternPool.Tag.struct_type), | |
| 319 | 331 | simple_type = @intFromEnum(InternPool.Tag.simple_type), |
| 320 | 332 | }; |
| 321 | 333 | |
| ... | ... | @@ -336,12 +348,18 @@ pub const Type = enum(u32) { |
| 336 | 348 | } |
| 337 | 349 | |
| 338 | 350 | pub fn tag(ty: Type, ip: *const InternPool) Type.Tag { |
| 351 | if (ty == .void) return .simple_type; | |
| 339 | 352 | return @enumFromInt(@intFromEnum(ty.value().valueTag(ip))); |
| 340 | 353 | } |
| 341 | 354 | |
| 342 | pub fn toFunctionPrototype(ty: Type, ip: *const InternPool) ?Key.TypeFunction { | |
| 343 | if (ty.tag(ip) != .type_function) return null; | |
| 344 | return ip.indexToKey(ty.value()).type_function; | |
| 355 | pub fn toFunctionPrototype(ty: Type, ip: *const InternPool) ?Key.FunctionType { | |
| 356 | if (ty.tag(ip) != .function_type) return null; | |
| 357 | return ip.valueToKey(ty.value()).function_type; | |
| 358 | } | |
| 359 | ||
| 360 | pub fn toStructType(ty: Type, ip: *const InternPool) ?Key.StructType { | |
| 361 | if (ty.tag(ip) != .struct_type) return null; | |
| 362 | return ip.valueToKey(ty.value()).struct_type; | |
| 345 | 363 | } |
| 346 | 364 | |
| 347 | 365 | pub fn hasRuntimeRepresentation(ty: Type, ip: *const InternPool) bool { |
| ... | ... | @@ -359,7 +377,8 @@ pub const Type = enum(u32) { |
| 359 | 377 | => false, |
| 360 | 378 | else => switch (ty.tag(ip)) { |
| 361 | 379 | .simple_type => unreachable, // all simple types are already printed |
| 362 | .type_function => true, | |
| 380 | .function_type => true, | |
| 381 | .struct_type => true, | |
| 363 | 382 | }, |
| 364 | 383 | }; |
| 365 | 384 | } |
| ... | ... | @@ -396,20 +415,46 @@ pub const Type = enum(u32) { |
| 396 | 415 | |
| 397 | 416 | switch (ty.tag(ip)) { |
| 398 | 417 | .simple_type => unreachable, // all simple types are already printed |
| 399 | .type_function => { | |
| 400 | const type_function = ty.toFunctionPrototype(ip).?; | |
| 418 | .function_type => { | |
| 419 | const function_type = ty.toFunctionPrototype(ip).?; | |
| 401 | 420 | try writer.writeAll("fn("); |
| 402 | for (type_function.param_types, 0..) |param, i| { | |
| 421 | for (function_type.param_types, 0..) |param, i| { | |
| 403 | 422 | if (i != 0) try writer.writeAll(", "); |
| 404 | 423 | try writer.print("{}", .{param.displayName(ip)}); |
| 405 | 424 | } |
| 406 | 425 | try writer.writeAll(") -> "); |
| 407 | try writer.print("{}", .{type_function.return_type.displayName(ip)}); | |
| 426 | try writer.print("{}", .{function_type.return_type.displayName(ip)}); | |
| 427 | }, | |
| 428 | .struct_type => { | |
| 429 | try writer.writeAll("[struct]"); | |
| 408 | 430 | }, |
| 409 | 431 | } |
| 410 | 432 | } |
| 411 | 433 | }; |
| 412 | 434 | |
| 435 | pub const Item = struct { | |
| 436 | tag: Tag, | |
| 437 | data: EncodedData, | |
| 438 | ||
| 439 | pub fn encode(comptime tag: Tag, data: tag.Data()) Item { | |
| 440 | return .{ .tag = tag, .data = EncodedData.encode(tag, data) }; | |
| 441 | } | |
| 442 | }; | |
| 443 | ||
| 444 | pub const ExtraStore = dod.ExtraStore(.intern_pool, .@"var"); | |
| 445 | pub const EncodedData = dod.EncodedData(Tag, u32); | |
| 446 | pub const ExtraIndex = ExtraStore.Index; | |
| 447 | ||
| 448 | /// How many items in the InternPool are statically known. | |
| 449 | /// This is specified with an integer literal and a corresponding comptime | |
| 450 | /// assert below to break an unfortunate and arguably incorrect dependency loop | |
| 451 | /// when compiling. | |
| 452 | pub const static_len = Teg.Inst.Index.static_len; | |
| 453 | comptime { | |
| 454 | // @compileLog(static_len, static_keys.len); | |
| 455 | assert(static_len == static_keys.len); | |
| 456 | } | |
| 457 | ||
| 413 | 458 | pub const static_keys = [_]Key{ |
| 414 | 459 | SimpleType.@"opaque".key(), |
| 415 | 460 | SimpleType.bool.key(), |
| ... | ... | @@ -434,29 +479,27 @@ pub const static_keys = [_]Key{ |
| 434 | 479 | pub const FuncAnalysis = packed struct(u32) { |
| 435 | 480 | state: State, |
| 436 | 481 | |
| 437 | _: u24 = 0, | |
| 438 | ||
| 439 | pub const State = enum(u8) { | |
| 440 | /// This function has not yet undergone analysis, because we have not | |
| 441 | /// seen a potential runtime call. It may be analyzed in future. | |
| 482 | pub const State = enum(u32) { | |
| 442 | 483 | none, |
| 443 | /// Analysis for this function has been queued, but not yet completed. | |
| 444 | queued, | |
| 445 | /// This function intentionally only has ZIR generated because it is marked | |
| 446 | /// inline, which means no runtime version of the function will be generated. | |
| 447 | inline_only, | |
| 448 | in_progress, | |
| 449 | /// There will be a corresponding ErrorMsg in Module.failed_decls | |
| 450 | sema_failure, | |
| 451 | /// This function might be OK but it depends on another Decl which did not | |
| 452 | /// successfully complete semantic analysis. | |
| 453 | dependency_failure, | |
| 454 | /// There will be a corresponding ErrorMsg in Module.failed_decls. | |
| 455 | /// Indicates that semantic analysis succeeded, but code generation for | |
| 456 | /// this function failed. | |
| 457 | codegen_failure, | |
| 458 | /// Semantic analysis and code generation of this function succeeded. | |
| 459 | success, | |
| 484 | // /// This function has not yet undergone analysis, because we have not | |
| 485 | // /// seen a potential runtime call. It may be analyzed in future. none, | |
| 486 | // /// Analysis for this function has been queued, but not yet completed. | |
| 487 | // queued, | |
| 488 | // /// This function intentionally only has ZIR generated because it is marked | |
| 489 | // /// inline, which means no runtime version of the function will be generated. | |
| 490 | // inline_only, | |
| 491 | // in_progress, | |
| 492 | // /// There will be a corresponding ErrorMsg in Module.failed_decls | |
| 493 | // sema_failure, | |
| 494 | // /// This function might be OK but it depends on another Decl which did not | |
| 495 | // /// successfully complete semantic analysis. | |
| 496 | // dependency_failure, | |
| 497 | // /// There will be a corresponding ErrorMsg in Module.failed_decls. | |
| 498 | // /// Indicates that semantic analysis succeeded, but code generation for | |
| 499 | // /// this function failed. | |
| 500 | // codegen_failure, | |
| 501 | // /// Semantic analysis and code generation of this function succeeded. | |
| 502 | // success, | |
| 460 | 503 | }; |
| 461 | 504 | }; |
| 462 | 505 | |
| ... | ... | @@ -557,6 +600,7 @@ pub const NullTerminatedString = enum(u32) { |
| 557 | 600 | string: NullTerminatedString, |
| 558 | 601 | ip: *const InternPool, |
| 559 | 602 | }; |
| 603 | ||
| 560 | 604 | fn format( |
| 561 | 605 | data: FormatData, |
| 562 | 606 | comptime specifier: []const u8, |
| ... | ... | @@ -574,6 +618,15 @@ pub const NullTerminatedString = enum(u32) { |
| 574 | 618 | pub fn fmt(self: NullTerminatedString, ip: *const InternPool) std.fmt.Formatter(format) { |
| 575 | 619 | return .{ .data = .{ .string = self, .ip = ip } }; |
| 576 | 620 | } |
| 621 | ||
| 622 | pub const Slice = struct { | |
| 623 | start: u32, | |
| 624 | len: u32, | |
| 625 | ||
| 626 | pub fn get(slice: Slice, ip: *const InternPool) []NullTerminatedString { | |
| 627 | return @ptrCast(ip.extra.list.items[slice.start..][0..slice.len]); | |
| 628 | } | |
| 629 | }; | |
| 577 | 630 | }; |
| 578 | 631 | |
| 579 | 632 | pub const TrackedInst = extern struct { |
| ... | ... | @@ -721,14 +774,18 @@ const SimpleType = enum { |
| 721 | 774 | }; |
| 722 | 775 | |
| 723 | 776 | pub const Key = union(enum) { |
| 777 | // Values | |
| 724 | 778 | comptime_float: f64, |
| 725 | 779 | int32: i32, |
| 726 | 780 | float: f64, |
| 727 | 781 | string: []const u8, |
| 728 | 782 | simple_value: SimpleValue, |
| 729 | simple_type: SimpleType, | |
| 730 | type_function: TypeFunction, | |
| 783 | struct_value: StructValue, | |
| 731 | 784 | func: Func, |
| 785 | // Types | |
| 786 | simple_type: SimpleType, | |
| 787 | function_type: FunctionType, | |
| 788 | struct_type: StructType, | |
| 732 | 789 | |
| 733 | 790 | const Adapter = struct { |
| 734 | 791 | intern_pool: *const InternPool, |
| ... | ... | @@ -736,7 +793,7 @@ pub const Key = union(enum) { |
| 736 | 793 | pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: usize) bool { |
| 737 | 794 | _ = b_void; |
| 738 | 795 | if (ctx.intern_pool.items.items(.tag)[b_map_index] == .removed) return false; |
| 739 | return ctx.intern_pool.indexToKey(@enumFromInt(b_map_index)).eql(a, ctx.intern_pool); | |
| 796 | return ctx.intern_pool.valueToKey(@enumFromInt(b_map_index)).eql(a, ctx.intern_pool); | |
| 740 | 797 | } |
| 741 | 798 | |
| 742 | 799 | pub fn hash(ctx: @This(), a: Key) u32 { |
| ... | ... | @@ -744,11 +801,11 @@ pub const Key = union(enum) { |
| 744 | 801 | } |
| 745 | 802 | }; |
| 746 | 803 | |
| 747 | pub const TypeFunction = struct { | |
| 804 | pub const FunctionType = struct { | |
| 748 | 805 | param_types: []Type, |
| 749 | 806 | return_type: Type, |
| 750 | 807 | |
| 751 | pub fn hash(self: TypeFunction, hasher: *Hash) void { | |
| 808 | pub fn hash(self: FunctionType, hasher: *Hash) void { | |
| 752 | 809 | const t = tracer.trace(@src()); |
| 753 | 810 | defer t.end(); |
| 754 | 811 | |
| ... | ... | @@ -758,12 +815,32 @@ pub const Key = union(enum) { |
| 758 | 815 | std.hash.autoHash(hasher, self.return_type); |
| 759 | 816 | } |
| 760 | 817 | |
| 761 | pub fn eql(a: TypeFunction, b: TypeFunction) bool { | |
| 818 | pub fn eql(a: FunctionType, b: FunctionType) bool { | |
| 762 | 819 | return std.mem.eql(Type, a.param_types, b.param_types) and |
| 763 | 820 | a.return_type == b.return_type; |
| 764 | 821 | } |
| 765 | 822 | }; |
| 766 | 823 | |
| 824 | pub const StructType = struct { | |
| 825 | field_keys: []NullTerminatedString, | |
| 826 | field_types: []const Type, | |
| 827 | ||
| 828 | pub fn hash(self: StructType, hasher: *Hash) void { | |
| 829 | // TODO: remove this function. structural typing is not used | |
| 830 | std.hash.autoHashStrat(hasher, self, .DeepRecursive); | |
| 831 | } | |
| 832 | }; | |
| 833 | ||
| 834 | pub const StructValue = struct { | |
| 835 | ty: Type, | |
| 836 | field_values: []const Value, | |
| 837 | ||
| 838 | pub fn hash(self: StructValue, hasher: *Hash) void { | |
| 839 | std.hash.autoHash(hasher, self.ty); | |
| 840 | std.hash.autoHashStrat(hasher, self.field_values, .Deep); | |
| 841 | } | |
| 842 | }; | |
| 843 | ||
| 767 | 844 | pub const Func = struct { |
| 768 | 845 | ty: Type, |
| 769 | 846 | owner_decl: DeclIndex, |
| ... | ... | @@ -781,10 +858,10 @@ pub const Key = union(enum) { |
| 781 | 858 | } |
| 782 | 859 | |
| 783 | 860 | pub fn hash64(key: Key, ip: *const InternPool) u64 { |
| 861 | _ = ip; // autofix | |
| 784 | 862 | const t = tracer.trace(@src()); |
| 785 | 863 | defer t.end(); |
| 786 | 864 | |
| 787 | _ = ip; // autofix | |
| 788 | 865 | const asBytes = std.mem.asBytes; |
| 789 | 866 | const KeyTag = @typeInfo(Key).Union.tag_type.?; |
| 790 | 867 | const seed = @intFromEnum(@as(KeyTag, key)); |
| ... | ... | @@ -797,7 +874,7 @@ pub const Key = union(enum) { |
| 797 | 874 | .string, |
| 798 | 875 | => |x| Hash.hash(seed, x), |
| 799 | 876 | .float, .comptime_float => |x| Hash.hash(seed, asBytes(&Float.assertPure(x))), |
| 800 | .type_function => |x| { | |
| 877 | .function_type => |x| { | |
| 801 | 878 | var hasher = Hash.init(seed); |
| 802 | 879 | x.hash(&hasher); |
| 803 | 880 | return hasher.final(); |
| ... | ... | @@ -807,14 +884,24 @@ pub const Key = union(enum) { |
| 807 | 884 | x.hash(&hasher); |
| 808 | 885 | return hasher.final(); |
| 809 | 886 | }, |
| 887 | .struct_type => |x| { | |
| 888 | var hasher = Hash.init(seed); | |
| 889 | x.hash(&hasher); | |
| 890 | return hasher.final(); | |
| 891 | }, | |
| 892 | .struct_value => |x| { | |
| 893 | var hasher = Hash.init(seed); | |
| 894 | x.hash(&hasher); | |
| 895 | return hasher.final(); | |
| 896 | }, | |
| 810 | 897 | }; |
| 811 | 898 | } |
| 812 | 899 | |
| 813 | 900 | pub fn eql(a: Key, b: Key, ip: *const InternPool) bool { |
| 901 | _ = ip; // autofix | |
| 814 | 902 | const t = tracer.trace(@src()); |
| 815 | 903 | defer t.end(); |
| 816 | 904 | |
| 817 | _ = ip; // autofix | |
| 818 | 905 | const KeyTag = @typeInfo(Key).Union.tag_type.?; |
| 819 | 906 | const a_tag: KeyTag = a; |
| 820 | 907 | const b_tag: KeyTag = b; |
| ... | ... | @@ -828,7 +915,7 @@ pub const Key = union(enum) { |
| 828 | 915 | => |x| std.mem.eql(u8, x, b.string), |
| 829 | 916 | .float => |x| Float.assertPure(x) == Float.assertPure(b.float), |
| 830 | 917 | .comptime_float => |x| Float.assertPure(x) == Float.assertPure(b.comptime_float), |
| 831 | .type_function => |x| x.eql(b.type_function), | |
| 918 | .function_type => |x| x.eql(b.function_type), | |
| 832 | 919 | .func => |a_info| { |
| 833 | 920 | const b_info = b.func; |
| 834 | 921 | if (a_info.owner_decl != b_info.owner_decl) |
| ... | ... | @@ -839,6 +926,14 @@ pub const Key = union(enum) { |
| 839 | 926 | // TODO: zig has something extra here for inferred error sets |
| 840 | 927 | return false; |
| 841 | 928 | }, |
| 929 | .struct_type => { | |
| 930 | return false; // structs are nomial | |
| 931 | }, | |
| 932 | .struct_value => |a_info| { | |
| 933 | const b_info = b.struct_value; | |
| 934 | return a_info.ty == b_info.ty and | |
| 935 | std.mem.eql(Value, a_info.field_values, b_info.field_values); | |
| 936 | }, | |
| 842 | 937 | }; |
| 843 | 938 | } |
| 844 | 939 | }; |
| ... | ... | @@ -890,8 +985,8 @@ pub fn init(ip: *InternPool, gpa: Allocator) !void { |
| 890 | 985 | |
| 891 | 986 | if (std.debug.runtime_safety) { |
| 892 | 987 | // Sanity check. |
| 893 | assert(ip.indexToKey(.bool_true).simple_value == .true); | |
| 894 | assert(ip.indexToKey(.bool_false).simple_value == .false); | |
| 988 | assert(ip.valueToKey(.bool_true).simple_value == .true); | |
| 989 | assert(ip.valueToKey(.bool_false).simple_value == .false); | |
| 895 | 990 | } |
| 896 | 991 | |
| 897 | 992 | assert(ip.items.len == static_keys.len); |
| ... | ... | @@ -944,22 +1039,20 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { |
| 944 | 1039 | ip.* = undefined; |
| 945 | 1040 | } |
| 946 | 1041 | |
| 947 | pub fn indexToKey(ip: *const InternPool, index: Value) Key { | |
| 1042 | pub fn valueToKey(ip: *const InternPool, index: Value) Key { | |
| 948 | 1043 | assert(index != .none); |
| 949 | 1044 | const item: Item = ip.items.get(@intFromEnum(index)); |
| 950 | 1045 | const data = item.data; |
| 951 | 1046 | return switch (item.tag) { |
| 952 | 1047 | .removed => unreachable, |
| 953 | 1048 | |
| 954 | .type_function => .{ | |
| 955 | .type_function = ip.extraTypeFunction(data.decode(.type_function, item.tag)), | |
| 956 | }, | |
| 957 | ||
| 958 | 1049 | .simple_type => .{ .simple_type = data.decode(.simple_type, item.tag) }, |
| 959 | 1050 | .simple_value => .{ .simple_value = data.decode(.simple_value, item.tag) }, |
| 960 | 1051 | .int32 => .{ .int32 = data.decode(.int32, item.tag) }, |
| 961 | 1052 | .float => .{ .float = data.decode(.float, item.tag).get(ip).decode() }, |
| 962 | .comptime_float => .{ .comptime_float = data.decode(.comptime_float, item.tag).get(ip).decode() }, | |
| 1053 | .comptime_float => .{ | |
| 1054 | .comptime_float = data.decode(.comptime_float, item.tag).get(ip).decode(), | |
| 1055 | }, | |
| 963 | 1056 | .string => .{ .string = data.decode(.string, item.tag).get(ip).toSlice(ip) }, |
| 964 | 1057 | .func => { |
| 965 | 1058 | const func = data.decode(.func, item.tag).get(ip); |
| ... | ... | @@ -969,6 +1062,29 @@ pub fn indexToKey(ip: *const InternPool, index: Value) Key { |
| 969 | 1062 | .teg_body_inst = func.teg_body_inst, |
| 970 | 1063 | } }; |
| 971 | 1064 | }, |
| 1065 | .function_type => .{ | |
| 1066 | .function_type = ip.extraTypeFunction(data.decode(.function_type, item.tag)), | |
| 1067 | }, | |
| 1068 | .struct_type => { | |
| 1069 | const payload: Tag.TypeStruct, var trail = | |
| 1070 | data.decode(.struct_type, item.tag).getTrail(ip); | |
| 1071 | ||
| 1072 | return .{ .struct_type = .{ | |
| 1073 | .field_keys = trail.readNullTerminatedStringSlice(payload.fields_len).get(ip), | |
| 1074 | .field_types = trail.readTypeSlice(payload.fields_len).get(ip), | |
| 1075 | } }; | |
| 1076 | }, | |
| 1077 | .struct_value => { | |
| 1078 | const payload: Tag.StructValue, var trail = | |
| 1079 | data.decode(.struct_value, item.tag).getTrail(ip); | |
| 1080 | ||
| 1081 | return .{ .struct_value = .{ | |
| 1082 | .ty = payload.ty, | |
| 1083 | .field_values = trail.readValueSlice( | |
| 1084 | payload.ty.value().valueData(ip, .struct_type).get(ip).fields_len, | |
| 1085 | ).get(ip), | |
| 1086 | } }; | |
| 1087 | }, | |
| 972 | 1088 | }; |
| 973 | 1089 | } |
| 974 | 1090 | |
| ... | ... | @@ -1020,20 +1136,39 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { |
| 1020 | 1136 | |
| 1021 | 1137 | break :brk Item.encode(.func, extra); |
| 1022 | 1138 | }, |
| 1023 | .type_function => |f| brk: { | |
| 1139 | .function_type => |f| brk: { | |
| 1024 | 1140 | const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TypeFunction, .{ |
| 1025 | 1141 | .params_len = @intCast(f.param_types.len), |
| 1026 | 1142 | .return_type = f.return_type, |
| 1027 | 1143 | }, f.param_types.len); |
| 1028 | 1144 | defer trail.end(); |
| 1029 | 1145 | trail.writeSlice(f.param_types); |
| 1030 | break :brk Item.encode(.type_function, extra); | |
| 1146 | break :brk Item.encode(.function_type, extra); | |
| 1147 | }, | |
| 1148 | .struct_type => |s| brk: { | |
| 1149 | assert(s.field_keys.len == s.field_types.len); | |
| 1150 | const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TypeStruct, .{ | |
| 1151 | .fields_len = @intCast(s.field_keys.len), | |
| 1152 | }, s.field_keys.len * 2); | |
| 1153 | trail.writeSlice(s.field_keys); | |
| 1154 | trail.writeSlice(s.field_types); | |
| 1155 | ||
| 1156 | break :brk Item.encode(.struct_type, extra); | |
| 1157 | }, | |
| 1158 | .struct_value => |s| brk: { | |
| 1159 | assert(s.ty.value().toKey(ip).struct_type.field_keys.len == s.field_values.len); | |
| 1160 | const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.StructValue, .{ | |
| 1161 | .ty = s.ty, | |
| 1162 | }, s.field_values.len); | |
| 1163 | trail.writeSlice(s.field_values); | |
| 1164 | break :brk Item.encode(.struct_value, extra); | |
| 1031 | 1165 | }, |
| 1032 | 1166 | }; |
| 1033 | 1167 | ip.items.appendAssumeCapacity(item); |
| 1034 | 1168 | const index: Value = @enumFromInt(ip.items.len - 1); |
| 1035 | 1169 | if (std.debug.runtime_safety) { |
| 1036 | assert(ip.indexToKey(index).eql(key, ip)); | |
| 1170 | const key_added = ip.valueToKey(index); | |
| 1171 | assert(key == .struct_type or key_added.eql(key, ip)); | |
| 1037 | 1172 | } |
| 1038 | 1173 | return index; |
| 1039 | 1174 | } |
| ... | ... | @@ -1041,11 +1176,11 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { |
| 1041 | 1176 | fn extraTypeFunction( |
| 1042 | 1177 | ip: *const InternPool, |
| 1043 | 1178 | extra_index: ExtraIndex(Tag.TypeFunction), |
| 1044 | ) Key.TypeFunction { | |
| 1045 | const type_function, var trail = extra_index.getTrail(ip); | |
| 1046 | const param_types = trail.readTypeSlice(type_function.params_len); | |
| 1047 | const result: Key.TypeFunction = .{ | |
| 1048 | .return_type = type_function.return_type, | |
| 1179 | ) Key.FunctionType { | |
| 1180 | const function_type, var trail = extra_index.getTrail(ip); | |
| 1181 | const param_types = trail.readTypeSlice(function_type.params_len); | |
| 1182 | const result: Key.FunctionType = .{ | |
| 1183 | .return_type = function_type.return_type, | |
| 1049 | 1184 | .param_types = param_types.get(ip), |
| 1050 | 1185 | }; |
| 1051 | 1186 | return result; |
| ... | ... | @@ -1130,6 +1265,10 @@ pub fn namespacePtr(ip: *InternPool, index: NamespaceIndex) *Comp.Namespace { |
| 1130 | 1265 | return ip.allocated_namespaces.at(@intFromEnum(index)); |
| 1131 | 1266 | } |
| 1132 | 1267 | |
| 1268 | pub fn namespacePtrConst(ip: *const InternPool, index: NamespaceIndex) *const Comp.Namespace { | |
| 1269 | return ip.allocated_namespaces.at(@intFromEnum(index)); | |
| 1270 | } | |
| 1271 | ||
| 1133 | 1272 | pub fn createDecl( |
| 1134 | 1273 | ip: *InternPool, |
| 1135 | 1274 | gpa: Allocator, |
| ... | ... | @@ -1222,7 +1361,7 @@ test InternPool { |
| 1222 | 1361 | try testing.expectEqual(str, str2); |
| 1223 | 1362 | |
| 1224 | 1363 | const int1 = try ip.get(a, .{ .int32 = 21483 }); |
| 1225 | try testing.expectEqual(@as(i32, 21483), ip.indexToKey(int1).int32); | |
| 1364 | try testing.expectEqual(@as(i32, 21483), ip.valueToKey(int1).int32); | |
| 1226 | 1365 | const int2 = try ip.get(a, .{ .int32 = 21483 }); |
| 1227 | 1366 | try testing.expectEqual(int1, int2); |
| 1228 | 1367 | } |
src/core/JsLink.zig deleted-733| ... | ... | @@ -1,733 +0,0 @@ |
| 1 | /// This is the JavaScript backend's linker. | |
| 2 | /// It takes in a compilation and a list of `JsTree`s and creates a JavaScript | |
| 3 | /// bundle out of them. | |
| 4 | comp: *Comp, | |
| 5 | ||
| 6 | /// Value is an index into `link_strings` | |
| 7 | decls: std.AutoHashMapUnmanaged(Decl.Index, LinkString.Index) = .{}, | |
| 8 | link_strings: std.ArrayHashMapUnmanaged( | |
| 9 | LinkString, | |
| 10 | GlobalOwner, | |
| 11 | LinkString.Context, | |
| 12 | true, | |
| 13 | ) = .{}, | |
| 14 | string_bytes: std.ArrayListUnmanaged(u8) = .{}, | |
| 15 | ||
| 16 | /// The first declaration is the function containing top-level code. | |
| 17 | pub fn linkJsCode( | |
| 18 | comp: *Comp, | |
| 19 | decls: []const Decl.Index, | |
| 20 | writer: anytype, | |
| 21 | ) !void { | |
| 22 | var t = tracer.trace(@src()); | |
| 23 | defer t.end(); | |
| 24 | ||
| 25 | // Arena allocator is used for the main flow | |
| 26 | var link: JsLink = .{ .comp = comp }; | |
| 27 | defer link.deinit(); | |
| 28 | ||
| 29 | // Temp allocator is used for extremely temporary string work. | |
| 30 | // var temp_sfa = std.heap.stackFallback(4096, comp.gpa); | |
| 31 | // const temp_alloc = temp_sfa.get(); | |
| 32 | ||
| 33 | // 1. Resolve all global identifiers. Function names must not shadow external | |
| 34 | // names. We also need this list prepared so that local identifier names do not | |
| 35 | // shadow functions or externals. | |
| 36 | const root_decl = comp.intern_pool.declPtrConst(decls[0]); | |
| 37 | assert(root_decl.kind == .named); | |
| 38 | assert(root_decl.name == .empty); | |
| 39 | ||
| 40 | for (root_decl.codegen.?.extern_symbols) |extern_symbol| { | |
| 41 | try link.reserveExternSymbol(extern_symbol); | |
| 42 | } | |
| 43 | ||
| 44 | for (decls[1..]) |decl_index| { | |
| 45 | assert(decl_index != decls[0]); | |
| 46 | ||
| 47 | const decl = comp.intern_pool.declPtrConst(decl_index); | |
| 48 | ||
| 49 | // todo: add all externs first | |
| 50 | for (decl.codegen.?.extern_symbols) |extern_symbol| { | |
| 51 | try link.reserveExternSymbol(extern_symbol); | |
| 52 | } | |
| 53 | ||
| 54 | const name = decl.name.toSlice(&comp.intern_pool); | |
| 55 | assert(name.len > 0); | |
| 56 | ||
| 57 | const gop = try link.link_strings.getOrPutContextAdapted( | |
| 58 | comp.gpa, | |
| 59 | name, | |
| 60 | LinkString.SliceContext{ .link = &link }, | |
| 61 | .{ .link = &link }, | |
| 62 | ); | |
| 63 | if (gop.found_existing) { | |
| 64 | @panic("TODO: handle this shadowing case."); | |
| 65 | } | |
| 66 | const name_str = try link.appendLinkString(name); | |
| 67 | gop.key_ptr.* = name_str; | |
| 68 | gop.value_ptr.* = .decl; | |
| 69 | try link.decls.putNoClobber(comp.gpa, decl_index, @enumFromInt(gop.index)); | |
| 70 | } | |
| 71 | ||
| 72 | try writer.print("// Language Compiler v0.1.3. Alpha quality software.\n", .{}); | |
| 73 | ||
| 74 | if (root_decl.codegen.?.nodes.len > 1) { | |
| 75 | // 2. Print every function decl | |
| 76 | for (decls[1..]) |decl_index| { | |
| 77 | try link.printDecl(decl_index, writer); | |
| 78 | } | |
| 79 | ||
| 80 | // 3. Print the top-level code | |
| 81 | try link.printDeclContents(&root_decl.codegen.?, writer); | |
| 82 | } else { | |
| 83 | try writer.print("// (program has no observable effects, thus an empty file)\n", .{}); | |
| 84 | } | |
| 85 | } | |
| 86 | ||
| 87 | fn deinit(link: *JsLink) void { | |
| 88 | var t = tracer.trace(@src()); | |
| 89 | defer t.end(); | |
| 90 | ||
| 91 | defer link.* = undefined; | |
| 92 | link.decls.deinit(link.comp.gpa); | |
| 93 | link.link_strings.deinit(link.comp.gpa); | |
| 94 | link.string_bytes.deinit(link.comp.gpa); | |
| 95 | } | |
| 96 | ||
| 97 | fn reserveExternSymbol(link: *JsLink, extern_symbol: String.WithLen) !void { | |
| 98 | var t = tracer.trace(@src()); | |
| 99 | defer t.end(); | |
| 100 | ||
| 101 | const name = extern_symbol.toSlice(&link.comp.intern_pool); | |
| 102 | const gop = try link.link_strings.getOrPutContextAdapted( | |
| 103 | link.comp.gpa, | |
| 104 | name, | |
| 105 | LinkString.SliceContext{ .link = link }, | |
| 106 | .{ .link = link }, | |
| 107 | ); | |
| 108 | ||
| 109 | if (gop.found_existing) { | |
| 110 | if (gop.value_ptr.* != .external) { | |
| 111 | @panic("TODO: Rename Decl"); | |
| 112 | } | |
| 113 | } | |
| 114 | gop.key_ptr.* = try link.appendLinkString(name); | |
| 115 | gop.value_ptr.* = .external; | |
| 116 | } | |
| 117 | ||
| 118 | fn appendLinkString(link: *JsLink, string: []const u8) !LinkString { | |
| 119 | var t = tracer.trace(@src()); | |
| 120 | defer t.end(); | |
| 121 | ||
| 122 | const start = link.string_bytes.items.len; | |
| 123 | try link.string_bytes.appendSlice(link.comp.gpa, string); | |
| 124 | return .{ .start = @intCast(start), .len = @intCast(string.len) }; | |
| 125 | } | |
| 126 | ||
| 127 | fn declIdentifier(link: *const JsLink, decl: Decl.Index) []const u8 { | |
| 128 | var t = tracer.trace(@src()); | |
| 129 | defer t.end(); | |
| 130 | ||
| 131 | return (link.decls.get(decl) orelse if (std.debug.runtime_safety) { | |
| 132 | std.debug.panic("Decl {d} ({}) is not present in link decl map", .{ | |
| 133 | @intFromEnum(decl), | |
| 134 | link.comp.intern_pool.declPtrConst(decl).name.fmt(&link.comp.intern_pool), | |
| 135 | }); | |
| 136 | } else unreachable).toString(link).slice(link); | |
| 137 | } | |
| 138 | ||
| 139 | fn printDecl(link: *JsLink, decl_index: Decl.Index, writer: anytype) !void { | |
| 140 | var t = tracer.trace(@src()); | |
| 141 | defer t.end(); | |
| 142 | ||
| 143 | try writer.writeAll("let "); | |
| 144 | try writer.writeAll(link.declIdentifier(decl_index)); | |
| 145 | try writer.writeAll(" = "); | |
| 146 | ||
| 147 | const decl = link.comp.intern_pool.declPtrConst(decl_index); | |
| 148 | const tree = &decl.codegen.?; | |
| 149 | ||
| 150 | if (tree.param_count != 1) | |
| 151 | try writer.writeAll("("); | |
| 152 | for (0..tree.param_count) |id| { | |
| 153 | try link.printLocal(@intCast(id), writer); | |
| 154 | if (id != tree.param_count - 1) try writer.writeAll(", "); | |
| 155 | } | |
| 156 | if (tree.param_count != 1) | |
| 157 | try writer.writeAll(")"); | |
| 158 | try writer.writeAll(" => "); | |
| 159 | ||
| 160 | const stmts = tree.mainStatements(); | |
| 161 | if (stmts.len == 1 and tree.nodeTag(stmts[0]) == .stmt_return) { | |
| 162 | if (tree.nodeData(.stmt_return, stmts[0]).operand.unwrap()) |operand| { | |
| 163 | try link.printExpr(tree, operand, writer); | |
| 164 | } else { | |
| 165 | try writer.writeAll("{}"); | |
| 166 | } | |
| 167 | } else { | |
| 168 | try writer.writeAll("{\n"); | |
| 169 | try link.printDeclContents(tree, writer); | |
| 170 | try writer.writeAll("}"); | |
| 171 | } | |
| 172 | ||
| 173 | try writer.writeAll(";\n"); | |
| 174 | } | |
| 175 | ||
| 176 | fn printDeclContents(link: *JsLink, tree: *const JsTree, writer: anytype) !void { | |
| 177 | var t = tracer.trace(@src()); | |
| 178 | defer t.end(); | |
| 179 | ||
| 180 | for (tree.mainStatements()) |stmt| { | |
| 181 | try link.printExpr(tree, stmt, writer); | |
| 182 | try writer.writeAll(";\n"); | |
| 183 | } | |
| 184 | } | |
| 185 | ||
| 186 | fn printBlockContents( | |
| 187 | link: *JsLink, | |
| 188 | tree: *const JsTree, | |
| 189 | list: JsTree.IndexList, | |
| 190 | writer: anytype, | |
| 191 | ) !void { | |
| 192 | var t = tracer.trace(@src()); | |
| 193 | defer t.end(); | |
| 194 | ||
| 195 | assert(list.slice(tree).len > 0); | |
| 196 | for (list.slice(tree)) |stmt| { | |
| 197 | try link.printExpr(tree, stmt, writer); | |
| 198 | switch (tree.nodeTag(stmt)) { | |
| 199 | .stmt_block, | |
| 200 | .stmt_if, | |
| 201 | => {}, | |
| 202 | else => try writer.writeAll(";"), | |
| 203 | } | |
| 204 | try writer.writeAll("\n"); | |
| 205 | } | |
| 206 | } | |
| 207 | ||
| 208 | fn printBlock(link: *JsLink, tree: *const JsTree, list: JsTree.IndexList, writer: anytype) !void { | |
| 209 | var t = tracer.trace(@src()); | |
| 210 | defer t.end(); | |
| 211 | ||
| 212 | if (list.len != 1) { | |
| 213 | try writer.writeAll("{"); | |
| 214 | try link.printBlockContents(tree, list, writer); | |
| 215 | try writer.writeAll("}"); | |
| 216 | } else { | |
| 217 | try link.printBlockContents(tree, list, writer); | |
| 218 | } | |
| 219 | } | |
| 220 | ||
| 221 | fn printExpr( | |
| 222 | link: *JsLink, | |
| 223 | tree: *const JsTree, | |
| 224 | node: Node.Index, | |
| 225 | writer: anytype, | |
| 226 | ) @TypeOf(writer).Error!void { | |
| 227 | return link.printExprLevel(tree, node, .comma, writer); | |
| 228 | } | |
| 229 | ||
| 230 | fn printExprLevel( | |
| 231 | link: *JsLink, | |
| 232 | tree: *const JsTree, | |
| 233 | node: Node.Index, | |
| 234 | level: Level, | |
| 235 | writer: anytype, | |
| 236 | ) @TypeOf(writer).Error!void { | |
| 237 | var t = tracer.trace(@src()); | |
| 238 | defer t.end(); | |
| 239 | ||
| 240 | switch (tree.nodeTag(node)) { | |
| 241 | .root => unreachable, | |
| 242 | ||
| 243 | .plain_value => { | |
| 244 | const plain_value: JsTree.PlainValue = tree.nodeData(.plain_value, node); | |
| 245 | switch (plain_value.value.toKey(&link.comp.intern_pool)) { | |
| 246 | // TODO: minification | |
| 247 | .int32 => |val| try writer.print("{d}", .{val}), | |
| 248 | // TODO: correct printing of many value types | |
| 249 | .float => |val| try writer.print("{d}", .{val}), | |
| 250 | .simple_value => |val| try writer.writeAll(switch (val) { | |
| 251 | .true => "true", | |
| 252 | .false => "false", | |
| 253 | // TODO: make codegen decide from null and undefined | |
| 254 | // instead of emitting void nodes. | |
| 255 | .void => "undefined", | |
| 256 | }), | |
| 257 | .string => |val| try writeString(val, writer), | |
| 258 | ||
| 259 | .comptime_float => unreachable, // was not lowered to float | |
| 260 | ||
| 261 | else => unreachable, // this value is not a plain_value | |
| 262 | } | |
| 263 | }, | |
| 264 | ||
| 265 | .local => { | |
| 266 | const ref: JsTree.LocalRef = tree.nodeData(.local, node); | |
| 267 | try link.printLocal(ref.id, writer); | |
| 268 | }, | |
| 269 | ||
| 270 | .global => { | |
| 271 | const ref: JsTree.GlobalRef = tree.nodeData(.global, node); | |
| 272 | try writer.writeAll(ref.name.toSlice(&link.comp.intern_pool)); | |
| 273 | }, | |
| 274 | ||
| 275 | .decl => { | |
| 276 | const ref: JsTree.DeclRef = tree.nodeData(.decl, node); | |
| 277 | try writer.writeAll(link.declIdentifier(ref.index)); | |
| 278 | }, | |
| 279 | ||
| 280 | .stmt_break => { | |
| 281 | const stmt: JsTree.StmtBreak = tree.nodeData(.stmt_break, node); | |
| 282 | try writer.writeAll("break"); | |
| 283 | if (true) { | |
| 284 | try writer.writeAll(" "); | |
| 285 | try link.printLabelIdentifier(stmt.label, writer); | |
| 286 | } | |
| 287 | }, | |
| 288 | ||
| 289 | .stmt_return => { | |
| 290 | const stmt: JsTree.StmtReturn = tree.nodeData(.stmt_return, node); | |
| 291 | try writer.writeAll("return"); | |
| 292 | if (stmt.operand.unwrap()) |operand| { | |
| 293 | try writer.writeAll(" "); | |
| 294 | try link.printExpr(tree, operand, writer); | |
| 295 | } | |
| 296 | }, | |
| 297 | ||
| 298 | .stmt_declare_local => { | |
| 299 | const stmt: JsTree.StmtDeclareLocal = tree.nodeData(.stmt_declare_local, node); | |
| 300 | try writer.writeAll("let "); | |
| 301 | try link.printLocal(stmt.identifier_index, writer); | |
| 302 | if (stmt.init.unwrap()) |init| { | |
| 303 | try writer.writeAll(" = "); | |
| 304 | try link.printExpr(tree, init, writer); | |
| 305 | } | |
| 306 | }, | |
| 307 | ||
| 308 | .stmt_if => { | |
| 309 | const stmt: JsTree.StmtIf = tree.nodeData(.stmt_if, node); | |
| 310 | const data = stmt.data.get(tree); | |
| 311 | try writer.writeAll("if ("); | |
| 312 | try link.printExpr(tree, stmt.cond, writer); | |
| 313 | try writer.writeAll(") "); | |
| 314 | if (data.flags.yes_label) { | |
| 315 | try link.printLabelIdentifier(data.flags.block_label, writer); | |
| 316 | try writer.writeAll(":"); | |
| 317 | } | |
| 318 | try link.printBlock(tree, data.yes, writer); | |
| 319 | if (data.no.len > 0) { | |
| 320 | try writer.writeAll(" else "); | |
| 321 | if (data.flags.no_label) { | |
| 322 | try link.printLabelIdentifier(data.flags.block_label, writer); | |
| 323 | try writer.writeAll(":"); | |
| 324 | } | |
| 325 | try link.printBlock(tree, data.no, writer); | |
| 326 | } | |
| 327 | }, | |
| 328 | ||
| 329 | .stmt_block => { | |
| 330 | const stmt: JsTree.StmtBlock = tree.nodeData(.stmt_block, node); | |
| 331 | try link.printLabelIdentifier(stmt.label, writer); | |
| 332 | try writer.writeAll(": {\n"); | |
| 333 | try link.printBlockContents(tree, stmt.content.get(tree), writer); | |
| 334 | try writer.writeAll("}"); | |
| 335 | }, | |
| 336 | ||
| 337 | inline .assign, | |
| 338 | .add, | |
| 339 | .sub, | |
| 340 | .mul, | |
| 341 | .div, | |
| 342 | .eq, | |
| 343 | .lt, | |
| 344 | .gt, | |
| 345 | .assign_add, | |
| 346 | .assign_sub, | |
| 347 | .assign_mul, | |
| 348 | .assign_div, | |
| 349 | => |tag| { | |
| 350 | const bin: JsTree.Bin = tree.nodeData(tag, node); | |
| 351 | const op: Operator = @field(Operator, @tagName(tag)); | |
| 352 | const inner_level = comptime op.level(); | |
| 353 | if (inner_level.needsParens(level)) | |
| 354 | try writer.writeAll("("); | |
| 355 | try link.printExprLevel(tree, bin.lhs, inner_level, writer); | |
| 356 | try writer.writeAll(" "); | |
| 357 | try writer.writeAll(comptime op.symbol()); | |
| 358 | try writer.writeAll(" "); | |
| 359 | try link.printExprLevel(tree, bin.rhs, inner_level, writer); | |
| 360 | if (inner_level.needsParens(level)) | |
| 361 | try writer.writeAll(")"); | |
| 362 | }, | |
| 363 | ||
| 364 | inline .negate, | |
| 365 | .bool_not, | |
| 366 | .pre_dec, | |
| 367 | .pre_inc, | |
| 368 | => |tag| { | |
| 369 | const un: JsTree.Un = tree.nodeData(tag, node); | |
| 370 | const op: Operator = @field(Operator, @tagName(tag)); | |
| 371 | try writer.writeAll(comptime op.symbol()); | |
| 372 | try link.printExpr(tree, un.operand, writer); | |
| 373 | }, | |
| 374 | ||
| 375 | .ternary => { | |
| 376 | const ternary: JsTree.Ternary = tree.nodeData(.ternary, node); | |
| 377 | const data = ternary.data.get(tree); | |
| 378 | try writer.writeAll("("); | |
| 379 | try link.printExpr(tree, ternary.cond, writer); | |
| 380 | try writer.writeAll(" ? "); | |
| 381 | try link.printExpr(tree, data.yes, writer); | |
| 382 | try writer.writeAll(" : "); | |
| 383 | try link.printExpr(tree, data.no, writer); | |
| 384 | try writer.writeAll(")"); | |
| 385 | }, | |
| 386 | ||
| 387 | .call => { | |
| 388 | const call: JsTree.Call = tree.nodeData(.call, node); | |
| 389 | const args = call.args.get(tree); | |
| 390 | ||
| 391 | try link.printExpr(tree, call.base, writer); | |
| 392 | try writer.writeAll("("); | |
| 393 | ||
| 394 | for (args.slice(tree), 0..) |arg, i| { | |
| 395 | if (i != 0) try writer.writeAll(", "); | |
| 396 | try link.printExpr(tree, arg, writer); | |
| 397 | } | |
| 398 | ||
| 399 | try writer.writeAll(")"); | |
| 400 | }, | |
| 401 | .new => { | |
| 402 | const call: JsTree.Call = tree.nodeData(.new, node); | |
| 403 | const args = call.args.get(tree); | |
| 404 | ||
| 405 | if (Level.new.needsParens(level)) | |
| 406 | try writer.writeAll("("); | |
| 407 | try writer.writeAll("new "); | |
| 408 | try link.printExpr(tree, call.base, writer); | |
| 409 | ||
| 410 | if (args.len > 0) { | |
| 411 | try writer.writeAll("("); | |
| 412 | ||
| 413 | for (args.slice(tree), 0..) |arg, i| { | |
| 414 | if (i != 0) try writer.writeAll(", "); | |
| 415 | try link.printExpr(tree, arg, writer); | |
| 416 | } | |
| 417 | ||
| 418 | try writer.writeAll(")"); | |
| 419 | } | |
| 420 | if (Level.new.needsParens(level)) | |
| 421 | try writer.writeAll(")"); | |
| 422 | }, | |
| 423 | ||
| 424 | .field => { | |
| 425 | const field: JsTree.Field = tree.nodeData(.field, node); | |
| 426 | ||
| 427 | try link.printExpr(tree, field.base, writer); | |
| 428 | try writer.writeAll("."); | |
| 429 | try writer.writeAll(field.field.toSlice(&link.comp.intern_pool)); | |
| 430 | }, | |
| 431 | } | |
| 432 | } | |
| 433 | ||
| 434 | pub fn canPrintWithoutEscape(c: u21) bool { | |
| 435 | const first_ascii_printable = 0x20; | |
| 436 | const last_ascii_printable = 0x7E; | |
| 437 | const first_high_surrogate = 0xD800; | |
| 438 | const last_low_surrogate = 0xDFFF; | |
| 439 | ||
| 440 | if (c <= last_ascii_printable) { | |
| 441 | return c >= first_ascii_printable and c != '\\' and c != '"'; | |
| 442 | } else { | |
| 443 | return c != 0xFEFF and (c < first_high_surrogate or c > last_low_surrogate); | |
| 444 | } | |
| 445 | } | |
| 446 | ||
| 447 | pub fn indexOfNeedsEscape(slice: []const u8) ?usize { | |
| 448 | var it = std.unicode.Wtf8Iterator{ .bytes = slice, .i = 0 }; | |
| 449 | var i: usize = 0; | |
| 450 | while (it.nextCodepoint()) |c| { | |
| 451 | if (!canPrintWithoutEscape(c)) | |
| 452 | return i; | |
| 453 | i = it.i; | |
| 454 | } | |
| 455 | return null; | |
| 456 | } | |
| 457 | ||
| 458 | // This function is loosely based on Bun's `writeJSONString`, which | |
| 459 | // is a Zig port of esbuild's `printUnquotedUTF16` function. | |
| 460 | // Both projects are licensed under MIT | |
| 461 | fn writeString(decoded: []const u8, writer: anytype) !void { | |
| 462 | try writer.writeByte('"'); | |
| 463 | ||
| 464 | var text = decoded; | |
| 465 | while (text.len > 0) { | |
| 466 | const width = std.unicode.utf8ByteSequenceLength(text[0]) catch | |
| 467 | unreachable; // string is encoded incorrectly | |
| 468 | ||
| 469 | const c: u21 = std.unicode.wtf8Decode(text[0..width]) catch | |
| 470 | unreachable; // string is encoded incorrectly | |
| 471 | ||
| 472 | if (canPrintWithoutEscape(c)) { | |
| 473 | if (indexOfNeedsEscape(text)) |j| { | |
| 474 | try writer.writeAll(text[0..j]); | |
| 475 | text = text[j..]; | |
| 476 | continue; | |
| 477 | } else { | |
| 478 | try writer.writeAll(text); | |
| 479 | break; | |
| 480 | } | |
| 481 | } | |
| 482 | ||
| 483 | text = text[width..]; | |
| 484 | ||
| 485 | switch (c) { | |
| 486 | // Special-case the bell character since it may cause dumping this file to | |
| 487 | // the terminal to make a sound, which is undesirable. Note that we can't | |
| 488 | // use an octal literal to print this shorter since octal literals are not | |
| 489 | // allowed in strict mode (or in template strings). | |
| 490 | 0x07 => { | |
| 491 | try writer.writeAll("\\x07"); | |
| 492 | }, | |
| 493 | 0x08 => { | |
| 494 | try writer.writeAll("\\b"); | |
| 495 | }, | |
| 496 | 0x0C => { | |
| 497 | try writer.writeAll("\\f"); | |
| 498 | }, | |
| 499 | '\n' => { | |
| 500 | try writer.writeAll("\\n"); | |
| 501 | }, | |
| 502 | std.ascii.control_code.cr => { | |
| 503 | try writer.writeAll("\\r"); | |
| 504 | }, | |
| 505 | std.ascii.control_code.vt => { | |
| 506 | try writer.writeAll("\\v"); | |
| 507 | }, | |
| 508 | '\\' => { | |
| 509 | try writer.writeAll("\\\\"); | |
| 510 | }, | |
| 511 | '"' => { | |
| 512 | try writer.writeAll("\\\""); | |
| 513 | }, | |
| 514 | '\t' => { | |
| 515 | try writer.writeAll("\\t"); | |
| 516 | }, | |
| 517 | ||
| 518 | else => { | |
| 519 | const hex_chars = "0123456789ABCDEF"; | |
| 520 | ||
| 521 | if (c < 0xFFFF) { | |
| 522 | const k = @as(usize, @intCast(c)); | |
| 523 | ||
| 524 | try writer.writeAll(&[_]u8{ | |
| 525 | '\\', | |
| 526 | 'u', | |
| 527 | hex_chars[(k >> 12) & 0xF], | |
| 528 | hex_chars[(k >> 8) & 0xF], | |
| 529 | hex_chars[(k >> 4) & 0xF], | |
| 530 | hex_chars[k & 0xF], | |
| 531 | }); | |
| 532 | } else { | |
| 533 | const first_high_surrogate = 0xD800; | |
| 534 | const first_low_surrogate = 0xDC00; | |
| 535 | ||
| 536 | const k = c - 0x10000; | |
| 537 | const lo = @as(usize, @intCast(first_high_surrogate + ((k >> 10) & 0x3FF))); | |
| 538 | const hi = @as(usize, @intCast(first_low_surrogate + (k & 0x3FF))); | |
| 539 | ||
| 540 | try writer.writeAll(&[_]u8{ | |
| 541 | '\\', | |
| 542 | 'u', | |
| 543 | hex_chars[lo >> 12], | |
| 544 | hex_chars[(lo >> 8) & 15], | |
| 545 | hex_chars[(lo >> 4) & 15], | |
| 546 | hex_chars[lo & 15], | |
| 547 | '\\', | |
| 548 | 'u', | |
| 549 | hex_chars[hi >> 12], | |
| 550 | hex_chars[(hi >> 8) & 15], | |
| 551 | hex_chars[(hi >> 4) & 15], | |
| 552 | hex_chars[hi & 15], | |
| 553 | }); | |
| 554 | } | |
| 555 | }, | |
| 556 | } | |
| 557 | } | |
| 558 | try writer.writeByte('"'); | |
| 559 | } | |
| 560 | ||
| 561 | fn printLocal(link: *JsLink, local: JsTree.LocalIndex, writer: anytype) !void { | |
| 562 | var t = tracer.trace(@src()); | |
| 563 | defer t.end(); | |
| 564 | ||
| 565 | _ = link; // todo: be smarter about this | |
| 566 | try writer.print("v{d}", .{local}); | |
| 567 | } | |
| 568 | ||
| 569 | fn printLabelIdentifier(link: *JsLink, local: JsTree.LocalIndex, writer: anytype) !void { | |
| 570 | var t = tracer.trace(@src()); | |
| 571 | defer t.end(); | |
| 572 | ||
| 573 | _ = link; // todo: be smarter about this | |
| 574 | try writer.print("label{d}", .{local}); | |
| 575 | } | |
| 576 | ||
| 577 | const GlobalOwner = enum { | |
| 578 | external, | |
| 579 | decl, | |
| 580 | }; | |
| 581 | ||
| 582 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence | |
| 583 | const Level = enum { | |
| 584 | comma, | |
| 585 | assignment, | |
| 586 | conditional, | |
| 587 | logical_or, | |
| 588 | logical_and, | |
| 589 | bitwise_or, | |
| 590 | bitwise_xor, | |
| 591 | bitwise_and, | |
| 592 | equality, | |
| 593 | relational, | |
| 594 | bitshift, | |
| 595 | additive, | |
| 596 | multiplicative, | |
| 597 | exponentiation, | |
| 598 | prefix, | |
| 599 | postfix, | |
| 600 | new, | |
| 601 | access_and_call, | |
| 602 | member, | |
| 603 | ||
| 604 | pub fn needsParens(inner_level: Level, outer_level: Level) bool { | |
| 605 | return @intFromEnum(outer_level) > @intFromEnum(inner_level); | |
| 606 | } | |
| 607 | }; | |
| 608 | ||
| 609 | const Operator = enum { | |
| 610 | assign, | |
| 611 | assign_add, | |
| 612 | assign_sub, | |
| 613 | assign_mul, | |
| 614 | assign_div, | |
| 615 | add, | |
| 616 | sub, | |
| 617 | mul, | |
| 618 | div, | |
| 619 | eq, | |
| 620 | lt, | |
| 621 | gt, | |
| 622 | negate, | |
| 623 | bool_not, | |
| 624 | pre_inc, | |
| 625 | pre_dec, | |
| 626 | ||
| 627 | pub fn level(op: Operator) Level { | |
| 628 | return switch (op) { | |
| 629 | .assign, | |
| 630 | .assign_add, | |
| 631 | .assign_sub, | |
| 632 | .assign_mul, | |
| 633 | .assign_div, | |
| 634 | => .assignment, | |
| 635 | .add, .sub => .additive, | |
| 636 | .mul, .div => .multiplicative, | |
| 637 | .eq => .equality, | |
| 638 | .lt, .gt => .relational, | |
| 639 | ||
| 640 | .pre_inc, | |
| 641 | .pre_dec, | |
| 642 | .bool_not, | |
| 643 | .negate, | |
| 644 | => .prefix, | |
| 645 | }; | |
| 646 | } | |
| 647 | ||
| 648 | pub fn symbol(op: Operator) []const u8 { | |
| 649 | return switch (op) { | |
| 650 | .assign => "=", | |
| 651 | .assign_add => "+=", | |
| 652 | .assign_sub => "-=", | |
| 653 | .assign_mul => "*=", | |
| 654 | .assign_div => "/=", | |
| 655 | .add => "+", | |
| 656 | .sub => "-", | |
| 657 | .mul => "*", | |
| 658 | .div => "/", | |
| 659 | .eq => "===", | |
| 660 | .lt => "<", | |
| 661 | .gt => ">", | |
| 662 | ||
| 663 | .pre_inc => "++", | |
| 664 | .pre_dec => "++", | |
| 665 | ||
| 666 | .negate => "-", | |
| 667 | .bool_not => "!", | |
| 668 | }; | |
| 669 | } | |
| 670 | }; | |
| 671 | ||
| 672 | const LinkString = struct { | |
| 673 | start: u32, | |
| 674 | len: u32, | |
| 675 | ||
| 676 | fn slice(str: LinkString, link: *const JsLink) []const u8 { | |
| 677 | return link.string_bytes.items[str.start..][0..str.len]; | |
| 678 | } | |
| 679 | ||
| 680 | const Index = enum(u32) { | |
| 681 | _, | |
| 682 | ||
| 683 | pub fn toString(index: Index, link: *const JsLink) LinkString { | |
| 684 | return link.link_strings.keys()[@intFromEnum(index)]; | |
| 685 | } | |
| 686 | }; | |
| 687 | ||
| 688 | const Context = struct { | |
| 689 | link: *const JsLink, | |
| 690 | ||
| 691 | pub fn hash(ctx: Context, str: LinkString) u32 { | |
| 692 | // assert(str.start + str.len < ctx.link.string_bytes.items.len); | |
| 693 | ||
| 694 | return @truncate(Wyhash.hash(0, str.slice(ctx.link))); | |
| 695 | } | |
| 696 | ||
| 697 | pub fn eql(ctx: Context, a: LinkString, b: LinkString, _: usize) bool { | |
| 698 | assert(a.start + a.len < ctx.link.string_bytes.items.len); | |
| 699 | assert(b.start + b.len < ctx.link.string_bytes.items.len); | |
| 700 | return a.start == b.start; | |
| 701 | } | |
| 702 | }; | |
| 703 | ||
| 704 | const SliceContext = struct { | |
| 705 | link: *const JsLink, | |
| 706 | ||
| 707 | pub fn hash(_: SliceContext, str: []const u8) u32 { | |
| 708 | return @truncate(Wyhash.hash(0, str)); | |
| 709 | } | |
| 710 | ||
| 711 | pub fn eql(ctx: SliceContext, a: []const u8, b: LinkString, _: usize) bool { | |
| 712 | // since the given string is not in string_bytes we do comparisons | |
| 713 | return std.mem.eql(u8, a, b.slice(ctx.link)); | |
| 714 | } | |
| 715 | }; | |
| 716 | }; | |
| 717 | ||
| 718 | const JsLink = @This(); | |
| 719 | const std = @import("std"); | |
| 720 | const assert = std.debug.assert; | |
| 721 | const Wyhash = std.hash.Wyhash; | |
| 722 | const log = std.log.scoped(.js_link); | |
| 723 | ||
| 724 | const Comp = @import("Comp.zig"); | |
| 725 | const Decl = Comp.Decl; | |
| 726 | ||
| 727 | const JsTree = @import("JsTree.zig"); | |
| 728 | const Node = JsTree.Node; | |
| 729 | ||
| 730 | const InternPool = @import("InternPool.zig"); | |
| 731 | const String = InternPool.String; | |
| 732 | ||
| 733 | const tracer = @import("tracer"); |
src/core/JsNodeGen.zig deleted-701| ... | ... | @@ -1,701 +0,0 @@ |
| 1 | //! Air -> JsTree | |
| 2 | gpa: Allocator, | |
| 3 | arena: Allocator, | |
| 4 | air: *const Air, | |
| 5 | ip: *const InternPool, | |
| 6 | ||
| 7 | nodes: Node.List, | |
| 8 | extra: dod.ExtraStore(.tfr, .@"var"), | |
| 9 | extern_symbols: std.AutoArrayHashMapUnmanaged(InternPool.String.WithLen, void), | |
| 10 | decl_deps: std.AutoArrayHashMapUnmanaged(Decl.Index, void), | |
| 11 | ||
| 12 | param_count: u32, | |
| 13 | air_node_map: []MapEntry, | |
| 14 | scratch: std.ArrayListUnmanaged(u32), | |
| 15 | next_id: u32, | |
| 16 | next_label: u32, | |
| 17 | ||
| 18 | const Error = error{ | |
| 19 | OutOfMemory, | |
| 20 | }; | |
| 21 | ||
| 22 | const MapEntry = union { | |
| 23 | none: void, | |
| 24 | node: Node.Index, | |
| 25 | block: packed struct { | |
| 26 | used: bool = false, | |
| 27 | index: u31, | |
| 28 | }, | |
| 29 | }; | |
| 30 | ||
| 31 | pub fn init(gpa: Allocator, arena: Allocator, ip: *const InternPool, air: *const Air) !JsNodeGen { | |
| 32 | const t = tracer.trace(@src()); | |
| 33 | defer t.end(); | |
| 34 | ||
| 35 | const air_node_map = try arena.alloc(MapEntry, air.insts.len); | |
| 36 | if (std.debug.runtime_safety) { | |
| 37 | @memset(air_node_map, .{ .none = {} }); | |
| 38 | } | |
| 39 | const param_count = air.paramCount(); | |
| 40 | return .{ | |
| 41 | .gpa = gpa, | |
| 42 | .arena = arena, | |
| 43 | .air = air, | |
| 44 | .ip = ip, | |
| 45 | .param_count = param_count, | |
| 46 | .nodes = .{}, | |
| 47 | .extern_symbols = .{}, | |
| 48 | .decl_deps = .{}, | |
| 49 | .air_node_map = air_node_map, | |
| 50 | .extra = .{}, | |
| 51 | .scratch = .{}, | |
| 52 | .next_id = param_count, | |
| 53 | .next_label = 0, | |
| 54 | }; | |
| 55 | } | |
| 56 | ||
| 57 | pub fn deinit(c: *JsNodeGen) void { | |
| 58 | const t = tracer.trace(@src()); | |
| 59 | defer t.end(); | |
| 60 | ||
| 61 | defer c.* = undefined; | |
| 62 | c.nodes.deinit(c.gpa); | |
| 63 | c.extra.deinit(c.gpa); | |
| 64 | c.extern_symbols.deinit(c.gpa); | |
| 65 | c.decl_deps.deinit(c.gpa); | |
| 66 | ||
| 67 | // arena can deinit the rest | |
| 68 | } | |
| 69 | ||
| 70 | pub fn finalize(c: *JsNodeGen) !JsTree { | |
| 71 | const t = tracer.trace(@src()); | |
| 72 | defer t.end(); | |
| 73 | ||
| 74 | // todo: errdefer will probably leave memory in a fully invalid state | |
| 75 | try c.nodes.setCapacity(c.gpa, c.nodes.len); | |
| 76 | ||
| 77 | var extra = try c.extra.toOwnedStore(c.gpa); | |
| 78 | errdefer extra.deinit(c.gpa); | |
| 79 | ||
| 80 | // todo: i dont like this dupe | |
| 81 | const extern_symbols = try c.gpa.dupe(InternPool.String.WithLen, c.extern_symbols.keys()); | |
| 82 | errdefer c.gpa.free(extern_symbols); | |
| 83 | ||
| 84 | const decl_deps = try c.gpa.dupe(Decl.Index, c.decl_deps.keys()); | |
| 85 | errdefer c.gpa.free(decl_deps); | |
| 86 | ||
| 87 | defer c.deinit(); | |
| 88 | ||
| 89 | return .{ | |
| 90 | .param_count = c.param_count, | |
| 91 | .nodes = c.nodes.toOwnedSlice(), | |
| 92 | .extern_symbols = extern_symbols, | |
| 93 | .decl_deps = decl_deps, | |
| 94 | .extra = extra, | |
| 95 | }; | |
| 96 | } | |
| 97 | ||
| 98 | pub fn build(c: *JsNodeGen) !void { | |
| 99 | const t = tracer.trace(@src()); | |
| 100 | defer t.end(); | |
| 101 | ||
| 102 | assert(c.air.instTag(.main_block) == .block); | |
| 103 | const statements = c.air.instData(.block, .main_block).slice(c.air); | |
| 104 | ||
| 105 | const scratch_top = c.scratch.items.len; | |
| 106 | defer c.scratch.shrinkRetainingCapacity(scratch_top); | |
| 107 | ||
| 108 | _ = try c.add(.root, undefined); | |
| 109 | ||
| 110 | if (statements.len > 0) { | |
| 111 | for (statements[0..c.param_count], 1..) |stmt, expected_id| { | |
| 112 | assert(@intFromEnum(stmt) == expected_id); | |
| 113 | assert(c.air.instTag(stmt) == .arg); | |
| 114 | c.air_node_map[@intFromEnum(stmt)] = .{ | |
| 115 | .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }), | |
| 116 | }; | |
| 117 | } | |
| 118 | ||
| 119 | for (statements[c.param_count..]) |stmt| { | |
| 120 | try c.codeGenInst(stmt); | |
| 121 | } | |
| 122 | } | |
| 123 | ||
| 124 | const items = c.scratch.items[scratch_top..]; | |
| 125 | const top = c.extra.list.items.len; | |
| 126 | try c.extra.list.appendSlice(c.gpa, items); | |
| 127 | c.nodes.items(.data)[0] = EncodedData.encode(.root, .{ | |
| 128 | .start = @intCast(top), | |
| 129 | .len = @intCast(items.len), | |
| 130 | }); | |
| 131 | } | |
| 132 | ||
| 133 | fn codeGenInst(c: *JsNodeGen, inst: Air.Inst.Index) Error!void { | |
| 134 | const t = tracer.traceFmt(@src(), "#{d} {s}", .{ @intFromEnum(inst), @tagName(c.air.instTag(inst)) }); | |
| 135 | defer t.end(); | |
| 136 | ||
| 137 | switch (c.air.instTag(inst)) { | |
| 138 | .arg => unreachable, // codegen must skip over these instructions. | |
| 139 | ||
| 140 | .block => try c.codeGenBlock(inst), | |
| 141 | ||
| 142 | .js_global_ref => { | |
| 143 | const data: InternPool.String.WithLen = c.air.instData(.js_global_ref, inst); | |
| 144 | try c.extern_symbols.put(c.gpa, data, {}); | |
| 145 | c.air_node_map[@intFromEnum(inst)] = .{ | |
| 146 | .node = try c.add(.global, .{ .name = data }), | |
| 147 | }; | |
| 148 | }, | |
| 149 | ||
| 150 | .memory => { | |
| 151 | const i = @intFromEnum(inst); | |
| 152 | const data: Air.Memory = c.air.instData(.memory, inst); | |
| 153 | const value: JsTree.Node.Index.Optional = if (data.init.isVoid()) | |
| 154 | .none | |
| 155 | else | |
| 156 | (try c.fetchExpression(data.init)).toOptional(); | |
| 157 | ||
| 158 | try c.addStmt(try c.add(.stmt_declare_local, .{ | |
| 159 | .identifier_index = c.next_id, | |
| 160 | .init = value, | |
| 161 | })); | |
| 162 | const id = try c.add(.local, .{ .id = c.next_id }); | |
| 163 | c.next_id += 1; | |
| 164 | c.air_node_map[i] = .{ .node = id }; | |
| 165 | }, | |
| 166 | ||
| 167 | inline .gt, | |
| 168 | .lt, | |
| 169 | .eq, | |
| 170 | .add, | |
| 171 | .sub, | |
| 172 | .mul, | |
| 173 | .div, | |
| 174 | => |tag| { | |
| 175 | const data: Air.Bin = c.air.instData(tag, inst); | |
| 176 | const node = try c.add(@field(JsTree.Tag, @tagName(tag)), .{ | |
| 177 | .lhs = try c.fetchExpression(data.lhs), | |
| 178 | .rhs = try c.fetchExpression(data.rhs), | |
| 179 | }); | |
| 180 | try c.markExpression(inst, node); | |
| 181 | }, | |
| 182 | ||
| 183 | .store => { | |
| 184 | const data: Air.Store = c.air.instData(.store, inst); | |
| 185 | ||
| 186 | const target = try c.fetchInstExpression(data.memory); | |
| 187 | ||
| 188 | if (data.operand.kind == .index) { | |
| 189 | const index = data.operand.decode().index; | |
| 190 | switch (c.air.instTag(index)) { | |
| 191 | inline .add, | |
| 192 | .sub, | |
| 193 | .mul, | |
| 194 | .div, | |
| 195 | => |tag| { | |
| 196 | const inner_data: Air.Bin = c.air.instData(tag, index); | |
| 197 | if (inner_data.lhs.eql(data.memory.toRef())) { | |
| 198 | if ((tag == .add or tag == .sub) and | |
| 199 | inner_data.rhs.kind == .value) | |
| 200 | { | |
| 201 | // if (inner_data.rhs.decode().value.toFloat(c.ip)) |f| { | |
| 202 | // if (f == 1) { | |
| 203 | // // convert this into ++ or -- | |
| 204 | // const node = try c.add( | |
| 205 | // if (tag == .add) | |
| 206 | // .pre_inc | |
| 207 | // else | |
| 208 | // .pre_dec, | |
| 209 | // .{ | |
| 210 | // .operand = target, | |
| 211 | // }, | |
| 212 | // ); | |
| 213 | // try c.markExpression(inst, node); | |
| 214 | ||
| 215 | // return; | |
| 216 | // } | |
| 217 | // } | |
| 218 | } | |
| 219 | ||
| 220 | // convert into a compound assignment | |
| 221 | const node = try c.add( | |
| 222 | @field(JsTree.Tag, "assign_" ++ @tagName(tag)), | |
| 223 | .{ | |
| 224 | .lhs = target, | |
| 225 | .rhs = try c.fetchExpression(inner_data.rhs), | |
| 226 | }, | |
| 227 | ); | |
| 228 | try c.markExpression(inst, node); | |
| 229 | ||
| 230 | return; | |
| 231 | } | |
| 232 | }, | |
| 233 | else => {}, | |
| 234 | } | |
| 235 | } | |
| 236 | ||
| 237 | const node = try c.add(.assign, .{ | |
| 238 | .lhs = target, | |
| 239 | .rhs = try c.fetchExpression(data.operand), | |
| 240 | }); | |
| 241 | try c.markExpression(inst, node); | |
| 242 | }, | |
| 243 | ||
| 244 | inline .negate, | |
| 245 | .bool_not, | |
| 246 | => |tag| { | |
| 247 | const data: Air.Un = c.air.instData(tag, inst); | |
| 248 | const node = try c.add(@field(JsTree.Tag, @tagName(tag)), .{ | |
| 249 | .operand = try c.fetchExpression(data.operand), | |
| 250 | }); | |
| 251 | try c.markExpression(inst, node); | |
| 252 | }, | |
| 253 | ||
| 254 | .field => { | |
| 255 | const data: Air.Field = c.air.instData(.field, inst); | |
| 256 | const node = try c.add(.field, .{ | |
| 257 | .base = try c.fetchExpression(data.base), | |
| 258 | .field = data.field, | |
| 259 | }); | |
| 260 | try c.markExpression(inst, node); | |
| 261 | }, | |
| 262 | ||
| 263 | inline .call, .new => |tag| { | |
| 264 | const data: Air.Call = c.air.instData(tag, inst); | |
| 265 | const call_data = data.call_data.get(c.air); | |
| 266 | ||
| 267 | try c.extra.list.ensureUnusedCapacity(c.gpa, call_data.args.len); | |
| 268 | const start = c.extra.list.items.len; | |
| 269 | c.extra.list.items.len += call_data.args.len; | |
| 270 | ||
| 271 | const items = c.extra.list.items[start..][0..call_data.args.len]; | |
| 272 | ||
| 273 | for (call_data.args.slice(c.air), items) |arg, *dest| { | |
| 274 | dest.* = @intFromEnum(try c.fetchExpression(arg)); | |
| 275 | } | |
| 276 | ||
| 277 | const node = try c.add(@field(JsTree.Tag, @tagName(tag)), .{ | |
| 278 | .base = try c.fetchExpression(data.base), | |
| 279 | .args = try c.extra.append(c.gpa, JsTree.IndexList, .{ | |
| 280 | .start = @intCast(start), | |
| 281 | .len = call_data.args.len, | |
| 282 | }), | |
| 283 | }); | |
| 284 | try c.markExpression(inst, node); | |
| 285 | }, | |
| 286 | ||
| 287 | .@"if" => { | |
| 288 | const data: Air.If = c.air.instData(.@"if", inst); | |
| 289 | const extra = data.data.get(c.air); | |
| 290 | var cond = try c.fetchExpression(data.cond); | |
| 291 | ||
| 292 | if (c.canLowerToExpression(extra.yes) and | |
| 293 | c.canLowerToExpression(extra.no)) | |
| 294 | { | |
| 295 | const yes = try c.lowerToExpression(extra.yes); | |
| 296 | const no = try c.lowerToExpression(extra.yes); | |
| 297 | ||
| 298 | const ternary = try c.add(.ternary, .{ | |
| 299 | .cond = cond, | |
| 300 | .data = try c.extra.append(c.gpa, JsTree.Ternary.Data, .{ | |
| 301 | .yes = yes, | |
| 302 | .no = no, | |
| 303 | }), | |
| 304 | }); | |
| 305 | ||
| 306 | try c.markExpression(inst, ternary); | |
| 307 | } else { | |
| 308 | const flags = c.air.instFlags(inst); | |
| 309 | const uses = flags.uses; | |
| 310 | ||
| 311 | const target = if (uses != .none) | |
| 312 | try c.addTemporary() | |
| 313 | else | |
| 314 | null; | |
| 315 | ||
| 316 | var yes = try c.codeGenBranch(extra.yes, target); | |
| 317 | var no = try c.codeGenBranch(extra.no, target); | |
| 318 | assert(!((yes == null) and (no == null))); // no "void else void" branches | |
| 319 | ||
| 320 | // when `yes` is void, swap the condition so that the `else` is omitted | |
| 321 | if (yes == null) { | |
| 322 | const temp_yes = yes; | |
| 323 | yes = no; | |
| 324 | no = temp_yes; | |
| 325 | cond = try c.add(.bool_not, .{ | |
| 326 | .operand = cond, | |
| 327 | }); | |
| 328 | } | |
| 329 | ||
| 330 | const main_expr = try c.add(.stmt_if, .{ | |
| 331 | .cond = cond, | |
| 332 | .data = try c.extra.append(c.gpa, JsTree.StmtIf.Data, .{ | |
| 333 | .yes = yes.?.list, | |
| 334 | .no = if (no) |branch| | |
| 335 | branch.list | |
| 336 | else | |
| 337 | .{ .start = 0, .len = 0 }, | |
| 338 | .flags = .{ | |
| 339 | .block_label = @intCast(c.next_label + 1), | |
| 340 | .yes_label = yes.?.requires_label, | |
| 341 | .no_label = if (no) |branch| branch.requires_label else false, | |
| 342 | }, | |
| 343 | }), | |
| 344 | }); | |
| 345 | ||
| 346 | try c.addStmt(main_expr); | |
| 347 | ||
| 348 | if (target) |node| { | |
| 349 | try c.markExpression(inst, node); | |
| 350 | } | |
| 351 | } | |
| 352 | }, | |
| 353 | ||
| 354 | .@"break" => { | |
| 355 | const brk: Air.Break = c.air.instData(.@"break", inst); | |
| 356 | ||
| 357 | const operand = try c.fetchExpression(brk.value); | |
| 358 | ||
| 359 | // break #0 -> return | |
| 360 | if (brk.block_ref == .main_block) { | |
| 361 | try c.addStmt(try c.add(.stmt_return, .{ | |
| 362 | .operand = operand.toOptional(), | |
| 363 | })); | |
| 364 | } else { | |
| 365 | const block = &c.air_node_map[@intFromEnum(brk.block_ref)].block; | |
| 366 | ||
| 367 | const block_insts = c.air.instData(.block, brk.block_ref).slice(c.air); | |
| 368 | if (block_insts[block_insts.len - 1] != inst) { | |
| 369 | block.used = true; | |
| 370 | } | |
| 371 | ||
| 372 | const assignment: Node.Index.Optional = @enumFromInt(c.scratch.items[block.index]); | |
| 373 | if (assignment.unwrap()) |target| { | |
| 374 | try c.addStmt(try c.add(.assign, .{ | |
| 375 | .lhs = target, | |
| 376 | .rhs = operand, | |
| 377 | })); | |
| 378 | } | |
| 379 | ||
| 380 | try c.addStmt(try c.add(.stmt_break, .{ | |
| 381 | .label = c.scratch.items[block.index + 1], | |
| 382 | })); | |
| 383 | } | |
| 384 | }, | |
| 385 | } | |
| 386 | } | |
| 387 | ||
| 388 | fn markExpression(c: *JsNodeGen, inst: Air.Inst.Index, node: Node.Index) !void { | |
| 389 | const t = tracer.trace(@src()); | |
| 390 | defer t.end(); | |
| 391 | ||
| 392 | const flags: Air.Flags = c.air.instFlags(inst); | |
| 393 | const i = @intFromEnum(inst); | |
| 394 | switch (flags.uses) { | |
| 395 | .none => { | |
| 396 | try c.addStmt(node); | |
| 397 | }, | |
| 398 | .single => { | |
| 399 | // TODO: this is not aware of side effect fences | |
| 400 | c.air_node_map[i] = .{ .node = node }; | |
| 401 | }, | |
| 402 | .multi => { | |
| 403 | const decl = try c.add(.stmt_declare_local, .{ | |
| 404 | .identifier_index = c.next_id, | |
| 405 | .init = node.toOptional(), | |
| 406 | }); | |
| 407 | try c.addStmt(decl); | |
| 408 | const id = try c.add(.local, .{ .id = c.next_id }); | |
| 409 | c.next_id += 1; | |
| 410 | c.air_node_map[i] = .{ .node = id }; | |
| 411 | }, | |
| 412 | } | |
| 413 | } | |
| 414 | ||
| 415 | fn fetchExpression(c: *JsNodeGen, ref: Air.Inst.Ref) !Node.Index { | |
| 416 | const t = tracer.trace(@src()); | |
| 417 | defer t.end(); | |
| 418 | ||
| 419 | return switch (ref.decode()) { | |
| 420 | .value => |v| c.valueToExpression(v), | |
| 421 | .index => |index| fetchInstExpression(c, index), | |
| 422 | }; | |
| 423 | } | |
| 424 | ||
| 425 | fn fetchInstExpression(c: *JsNodeGen, index: Air.Inst.Index) !Node.Index { | |
| 426 | const t = tracer.trace(@src()); | |
| 427 | defer t.end(); | |
| 428 | ||
| 429 | const data = c.air_node_map[@intFromEnum(index)]; | |
| 430 | if (std.debug.runtime_safety) { | |
| 431 | const flags: Air.Flags = c.air.instFlags(index); | |
| 432 | switch (flags.uses) { | |
| 433 | .none => unreachable, // incorrectly marked instruction as unused, | |
| 434 | .single => {}, | |
| 435 | .multi => assert(c.nodes.items(.tag)[@intFromEnum(data.node)] == .local), | |
| 436 | } | |
| 437 | } | |
| 438 | return data.node; | |
| 439 | } | |
| 440 | ||
| 441 | fn valueToExpression(c: *JsNodeGen, v: Value) !Node.Index { | |
| 442 | const t = tracer.trace(@src()); | |
| 443 | defer t.end(); | |
| 444 | ||
| 445 | const intern_tag = v.valueTag(c.ip); | |
| 446 | switch (intern_tag) { | |
| 447 | .removed => unreachable, // UAF | |
| 448 | ||
| 449 | .type_function, | |
| 450 | .simple_type, | |
| 451 | => unreachable, // Type values are not allowed here | |
| 452 | ||
| 453 | .int32, | |
| 454 | .float, | |
| 455 | .comptime_float, | |
| 456 | .string, | |
| 457 | .simple_value, | |
| 458 | => return try c.add(.plain_value, .{ .value = v }), | |
| 459 | ||
| 460 | // funcs always point to decls | |
| 461 | .func => { | |
| 462 | const func = v.toKey(c.ip).func; | |
| 463 | try c.decl_deps.put(c.gpa, func.owner_decl, {}); | |
| 464 | return try c.add(.decl, .{ .index = func.owner_decl }); | |
| 465 | }, | |
| 466 | } | |
| 467 | } | |
| 468 | ||
| 469 | fn addTemporary(c: *JsNodeGen) !Node.Index { | |
| 470 | const t = tracer.trace(@src()); | |
| 471 | defer t.end(); | |
| 472 | ||
| 473 | try c.addStmt(try c.add(.stmt_declare_local, .{ | |
| 474 | .identifier_index = c.next_id, | |
| 475 | .init = .none, | |
| 476 | })); | |
| 477 | const id = try c.add(.local, .{ .id = c.next_id }); | |
| 478 | c.next_id += 1; | |
| 479 | return id; | |
| 480 | } | |
| 481 | ||
| 482 | const Branch = struct { | |
| 483 | requires_label: bool, | |
| 484 | list: JsTree.IndexList, | |
| 485 | }; | |
| 486 | ||
| 487 | fn codeGenBranch(c: *JsNodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Branch { | |
| 488 | const t = tracer.trace(@src()); | |
| 489 | defer t.end(); | |
| 490 | ||
| 491 | const scratch_top = c.scratch.items.len; | |
| 492 | defer c.scratch.shrinkRetainingCapacity(scratch_top); | |
| 493 | ||
| 494 | c.next_label += 1; | |
| 495 | defer c.next_label -= 1; | |
| 496 | const this_label = c.next_label; | |
| 497 | ||
| 498 | var requires_label = false; | |
| 499 | var items = switch (inst.decode()) { | |
| 500 | .value => |v| items: { | |
| 501 | if (v == .void) | |
| 502 | return null; | |
| 503 | if (temporary) |temp| { | |
| 504 | const node = try c.valueToExpression(v); | |
| 505 | try c.addStmt(try c.add(.assign, .{ | |
| 506 | .lhs = temp, | |
| 507 | .rhs = node, | |
| 508 | })); | |
| 509 | } | |
| 510 | break :items c.scratch.items[scratch_top..]; | |
| 511 | }, | |
| 512 | .index => |i| items: { | |
| 513 | assert(c.air.instTag(i) == .block); | |
| 514 | c.air_node_map[@intFromEnum(i)] = .{ | |
| 515 | .block = .{ .index = @intCast(scratch_top) }, | |
| 516 | }; | |
| 517 | ||
| 518 | try c.scratch.appendSlice(c.arena, &.{ | |
| 519 | @intFromEnum(if (temporary) |temp| temp.toOptional() else .none), | |
| 520 | this_label, | |
| 521 | }); | |
| 522 | ||
| 523 | const block: Air.IndexList = c.air.instData(.block, i); | |
| 524 | for (block.slice(c.air)) |item| { | |
| 525 | try c.codeGenInst(item); | |
| 526 | } | |
| 527 | ||
| 528 | requires_label = c.air_node_map[@intFromEnum(i)].block.used; | |
| 529 | ||
| 530 | break :items c.scratch.items[scratch_top + 2 ..]; | |
| 531 | }, | |
| 532 | }; | |
| 533 | ||
| 534 | const last_node = c.scratch.items[c.scratch.items.len - 1]; | |
| 535 | if (c.nodes.items(.tag)[last_node] == .stmt_break and | |
| 536 | c.nodes.items(.data)[last_node].decode(.stmt_break, .stmt_break).label == this_label) | |
| 537 | { | |
| 538 | items.len -= 1; | |
| 539 | c.nodes.len -= 1; | |
| 540 | } | |
| 541 | ||
| 542 | const top = c.extra.list.items.len; | |
| 543 | try c.extra.list.appendSlice(c.gpa, items); | |
| 544 | return .{ | |
| 545 | .requires_label = requires_label, | |
| 546 | .list = .{ | |
| 547 | .start = @intCast(top), | |
| 548 | .len = @intCast(items.len), | |
| 549 | }, | |
| 550 | }; | |
| 551 | } | |
| 552 | ||
| 553 | fn codeGenBlock(c: *JsNodeGen, inst: Air.Inst.Index) !void { | |
| 554 | const t = tracer.trace(@src()); | |
| 555 | defer t.end(); | |
| 556 | ||
| 557 | const scratch_top = c.scratch.items.len; | |
| 558 | errdefer c.scratch.shrinkRetainingCapacity(scratch_top); | |
| 559 | ||
| 560 | c.next_label += 1; | |
| 561 | defer c.next_label -= 1; | |
| 562 | ||
| 563 | const next_label = c.next_label; | |
| 564 | ||
| 565 | try c.scratch.appendSlice(c.arena, &.{ | |
| 566 | // @intFromEnum(if (temporary) |temp| temp.toOptional() else .none), | |
| 567 | @intFromEnum(Node.Index.Optional.none), | |
| 568 | next_label, | |
| 569 | }); | |
| 570 | ||
| 571 | c.air_node_map[@intFromEnum(inst)] = .{ | |
| 572 | .block = .{ .index = @intCast(scratch_top) }, | |
| 573 | }; | |
| 574 | const block: Air.IndexList = c.air.instData(.block, inst); | |
| 575 | ||
| 576 | for (block.slice(c.air)) |item| { | |
| 577 | try c.codeGenInst(item); | |
| 578 | } | |
| 579 | ||
| 580 | const items = c.scratch.items[scratch_top + 2 ..]; | |
| 581 | const top = c.extra.list.items.len; | |
| 582 | try c.extra.list.appendSlice(c.gpa, items); | |
| 583 | c.scratch.shrinkRetainingCapacity(scratch_top); | |
| 584 | try c.addStmt(try c.add(.stmt_block, .{ | |
| 585 | .label = c.next_label, | |
| 586 | .content = try c.extra.append(c.gpa, JsTree.IndexList, .{ | |
| 587 | .start = @intCast(top), | |
| 588 | .len = @intCast(items.len), | |
| 589 | }), | |
| 590 | })); | |
| 591 | } | |
| 592 | ||
| 593 | // Ideally, we convert everything to an expression. This allows for much | |
| 594 | // more compact code. When that isnt possible (`return`, `while`, etc), | |
| 595 | // we detect that here and ensure to lower `if` to actual if statements, | |
| 596 | // and so on. | |
| 597 | fn canLowerToExpression(c: *const JsNodeGen, inst: Air.Inst.Ref) bool { | |
| 598 | const t = tracer.trace(@src()); | |
| 599 | defer t.end(); | |
| 600 | ||
| 601 | return switch (inst.decode()) { | |
| 602 | .value => true, | |
| 603 | .index => |index| switch (c.air.instTag(index)) { | |
| 604 | .js_global_ref, | |
| 605 | .call, | |
| 606 | .new, | |
| 607 | .arg, | |
| 608 | .field, | |
| 609 | .add, | |
| 610 | .sub, | |
| 611 | .mul, | |
| 612 | .div, | |
| 613 | .eq, | |
| 614 | .lt, | |
| 615 | .gt, | |
| 616 | .negate, | |
| 617 | .bool_not, | |
| 618 | .store, | |
| 619 | => true, | |
| 620 | ||
| 621 | // JavaScript variable declarations require statements. | |
| 622 | // Later, this could be made true if we can hoist temporaries up. | |
| 623 | .memory => false, | |
| 624 | ||
| 625 | // break/return must be a js statement | |
| 626 | .@"break", | |
| 627 | => false, | |
| 628 | ||
| 629 | .block => { | |
| 630 | // TODO: blocks that are | |
| 631 | // - all statements except last are lowerable | |
| 632 | // - the last statement is lowerable OR a break of this block | |
| 633 | return false; | |
| 634 | }, | |
| 635 | ||
| 636 | .@"if" => { | |
| 637 | const call = c.air.instData(.@"if", index); | |
| 638 | const data = call.data.get(c.air); | |
| 639 | ||
| 640 | return c.canLowerToExpression(call.cond) and | |
| 641 | c.canLowerToExpression(data.yes) and | |
| 642 | c.canLowerToExpression(data.no); | |
| 643 | }, | |
| 644 | }, | |
| 645 | }; | |
| 646 | } | |
| 647 | ||
| 648 | fn lowerToExpression(c: *JsNodeGen, ref: Air.Inst.Ref) !Node.Index { | |
| 649 | const t = tracer.trace(@src()); | |
| 650 | defer t.end(); | |
| 651 | ||
| 652 | assert(c.canLowerToExpression(ref)); | |
| 653 | return switch (ref.decode()) { | |
| 654 | .value => |v| try c.valueToExpression(v), | |
| 655 | .index => |index| { | |
| 656 | try c.codeGenInst(index); | |
| 657 | return c.fetchExpression(ref); | |
| 658 | }, | |
| 659 | }; | |
| 660 | } | |
| 661 | ||
| 662 | fn add(c: *JsNodeGen, comptime tag: JsTree.Tag, data: tag.Data()) !Node.Index { | |
| 663 | const t = tracer.trace(@src()); | |
| 664 | defer t.end(); | |
| 665 | ||
| 666 | try c.nodes.append(c.gpa, .{ | |
| 667 | .tag = tag, | |
| 668 | .data = EncodedData.encode(tag, data), | |
| 669 | }); | |
| 670 | return @enumFromInt(c.nodes.len - 1); | |
| 671 | } | |
| 672 | ||
| 673 | fn addStmt(c: *JsNodeGen, stmt: Node.Index) !void { | |
| 674 | const t = tracer.trace(@src()); | |
| 675 | defer t.end(); | |
| 676 | ||
| 677 | try c.scratch.append(c.arena, @intFromEnum(stmt)); | |
| 678 | } | |
| 679 | ||
| 680 | const JsNodeGen = @This(); | |
| 681 | ||
| 682 | const std = @import("std"); | |
| 683 | const assert = std.debug.assert; | |
| 684 | const Allocator = std.mem.Allocator; | |
| 685 | ||
| 686 | const InternPool = @import("InternPool.zig"); | |
| 687 | const Value = InternPool.Value; | |
| 688 | const NullTerminatedString = InternPool.NullTerminatedString; | |
| 689 | ||
| 690 | const Comp = @import("Comp.zig"); | |
| 691 | const Decl = Comp.Decl; | |
| 692 | ||
| 693 | const JsTree = @import("JsTree.zig"); | |
| 694 | const EncodedData = JsTree.EncodedData; | |
| 695 | const Node = JsTree.Node; | |
| 696 | const LocalIndex = JsTree.LocalIndex; | |
| 697 | ||
| 698 | const Air = @import("Air.zig"); | |
| 699 | ||
| 700 | const dod = @import("array.zig"); | |
| 701 | const tracer = @import("tracer"); |
src/core/JsTree.zig deleted-258| ... | ... | @@ -1,258 +0,0 @@ |
| 1 | //! JavaScript Function Representation is the last representation before javascript | |
| 2 | //! source bytes are created. One notable thing here is that all local identifiers | |
| 3 | //! are numbered, instead of being named. The first `param_count` local identifiers | |
| 4 | //! refer to parameters, and the rest refer to the respective block that defined them. | |
| 5 | ||
| 6 | param_count: u32, | |
| 7 | nodes: Node.List.Slice, | |
| 8 | extern_symbols: []String.WithLen, | |
| 9 | decl_deps: []Decl.Index, | |
| 10 | extra: ExtraStore, | |
| 11 | ||
| 12 | pub const LocalIndex = u32; | |
| 13 | pub const ExtraStore = dod.ExtraStore(.tfr, .@"const"); | |
| 14 | pub const EncodedData = dod.EncodedData(Tag, u64); | |
| 15 | pub const ExtraIndex = ExtraStore.Index; | |
| 16 | ||
| 17 | pub const Tag = enum { | |
| 18 | root, | |
| 19 | /// Only allow simple value types: int, float, string, bool | |
| 20 | plain_value, | |
| 21 | /// Reference to a local identifier (give an VarId) | |
| 22 | local, | |
| 23 | /// Reference to a global identifier (give a NullTerminatedString) | |
| 24 | global, | |
| 25 | /// Reference to a declaration (contains a Decl.Index) | |
| 26 | decl, | |
| 27 | // bin ops | |
| 28 | assign, | |
| 29 | assign_add, | |
| 30 | assign_sub, | |
| 31 | assign_mul, | |
| 32 | assign_div, | |
| 33 | add, | |
| 34 | sub, | |
| 35 | mul, | |
| 36 | div, | |
| 37 | eq, | |
| 38 | lt, | |
| 39 | gt, | |
| 40 | // exprs | |
| 41 | call, | |
| 42 | new, | |
| 43 | field, | |
| 44 | ternary, | |
| 45 | // prefix ops | |
| 46 | negate, | |
| 47 | bool_not, | |
| 48 | pre_inc, | |
| 49 | pre_dec, | |
| 50 | // stmts | |
| 51 | /// `let ... = ...` | |
| 52 | stmt_declare_local, | |
| 53 | /// `if (...) ... else ...` | |
| 54 | stmt_if, | |
| 55 | /// `break ... ...` | |
| 56 | stmt_break, | |
| 57 | /// `return ...` (break from block #0) | |
| 58 | stmt_return, | |
| 59 | /// `...: { ... }` | |
| 60 | stmt_block, | |
| 61 | ||
| 62 | pub fn Data(comptime tag: Tag) type { | |
| 63 | return switch (tag) { | |
| 64 | .root => IndexList, | |
| 65 | .plain_value => PlainValue, | |
| 66 | .local => LocalRef, | |
| 67 | .global => GlobalRef, | |
| 68 | .decl => DeclRef, | |
| 69 | .assign, | |
| 70 | .assign_add, | |
| 71 | .assign_sub, | |
| 72 | .assign_mul, | |
| 73 | .assign_div, | |
| 74 | .add, | |
| 75 | .sub, | |
| 76 | .mul, | |
| 77 | .div, | |
| 78 | .eq, | |
| 79 | .lt, | |
| 80 | .gt, | |
| 81 | => Bin, | |
| 82 | .ternary => Ternary, | |
| 83 | .negate, | |
| 84 | .bool_not, | |
| 85 | .pre_inc, | |
| 86 | .pre_dec, | |
| 87 | => Un, | |
| 88 | .call, .new => Call, | |
| 89 | .field => Field, | |
| 90 | .stmt_if => StmtIf, | |
| 91 | .stmt_break => StmtBreak, | |
| 92 | .stmt_return => StmtReturn, | |
| 93 | .stmt_declare_local => StmtDeclareLocal, | |
| 94 | .stmt_block => StmtBlock, | |
| 95 | }; | |
| 96 | } | |
| 97 | }; | |
| 98 | ||
| 99 | pub const Node = struct { | |
| 100 | tag: Tag, | |
| 101 | data: EncodedData, | |
| 102 | ||
| 103 | pub const List = std.MultiArrayList(Node); | |
| 104 | ||
| 105 | pub const Index = enum(u32) { | |
| 106 | root = 0, | |
| 107 | _, | |
| 108 | ||
| 109 | pub fn toOptional(i: Index) Optional { | |
| 110 | return @enumFromInt(@intFromEnum(i)); | |
| 111 | } | |
| 112 | ||
| 113 | pub const Optional = enum(u32) { | |
| 114 | none = std.math.maxInt(u32), | |
| 115 | _, | |
| 116 | ||
| 117 | pub fn unwrap(oi: Optional) ?Index { | |
| 118 | return if (oi == .none) | |
| 119 | null | |
| 120 | else | |
| 121 | @enumFromInt(@intFromEnum(oi)); | |
| 122 | } | |
| 123 | }; | |
| 124 | }; | |
| 125 | }; | |
| 126 | ||
| 127 | pub const PlainValue = struct { | |
| 128 | value: Value, | |
| 129 | unused: Unused = .zero, | |
| 130 | }; | |
| 131 | ||
| 132 | pub const LocalRef = struct { | |
| 133 | id: LocalIndex, | |
| 134 | unused: Unused = .zero, | |
| 135 | }; | |
| 136 | ||
| 137 | pub const GlobalRef = struct { | |
| 138 | name: InternPool.String.WithLen, | |
| 139 | }; | |
| 140 | ||
| 141 | pub const DeclRef = struct { | |
| 142 | index: Decl.Index, | |
| 143 | unused: Unused = .zero, | |
| 144 | }; | |
| 145 | ||
| 146 | pub const StmtDeclareLocal = struct { | |
| 147 | identifier_index: u32, | |
| 148 | init: Node.Index.Optional, | |
| 149 | }; | |
| 150 | ||
| 151 | pub const Bin = struct { | |
| 152 | lhs: Node.Index, | |
| 153 | rhs: Node.Index, | |
| 154 | }; | |
| 155 | ||
| 156 | pub const Un = struct { | |
| 157 | operand: Node.Index, | |
| 158 | unused: Unused = .zero, | |
| 159 | }; | |
| 160 | ||
| 161 | pub const Call = struct { | |
| 162 | base: Node.Index, | |
| 163 | args: ExtraIndex(IndexList), | |
| 164 | }; | |
| 165 | ||
| 166 | pub const Field = struct { | |
| 167 | base: Node.Index, | |
| 168 | field: NullTerminatedString, | |
| 169 | }; | |
| 170 | ||
| 171 | pub const IndexList = struct { | |
| 172 | start: u32, | |
| 173 | len: u32, | |
| 174 | ||
| 175 | pub fn slice(list: IndexList, tree: *const JsTree) []const Node.Index { | |
| 176 | return @ptrCast(tree.extra.list.items[list.start..][0..list.len]); | |
| 177 | } | |
| 178 | }; | |
| 179 | ||
| 180 | pub const StmtIf = struct { | |
| 181 | cond: Node.Index, | |
| 182 | data: ExtraIndex(Data), | |
| 183 | ||
| 184 | pub const Data = struct { | |
| 185 | yes: IndexList, | |
| 186 | no: IndexList, | |
| 187 | flags: Flags, | |
| 188 | }; | |
| 189 | ||
| 190 | pub const Flags = packed struct(u32) { | |
| 191 | block_label: u16, | |
| 192 | yes_label: bool, | |
| 193 | no_label: bool, | |
| 194 | _: u14 = 0, | |
| 195 | }; | |
| 196 | }; | |
| 197 | ||
| 198 | pub const Ternary = struct { | |
| 199 | cond: Node.Index, | |
| 200 | data: ExtraIndex(Data), | |
| 201 | ||
| 202 | pub const Data = struct { | |
| 203 | yes: Node.Index, | |
| 204 | no: Node.Index, | |
| 205 | }; | |
| 206 | }; | |
| 207 | ||
| 208 | pub const StmtBreak = struct { | |
| 209 | label: u32, | |
| 210 | unused: Unused = .zero, | |
| 211 | }; | |
| 212 | ||
| 213 | pub const StmtReturn = struct { | |
| 214 | operand: Node.Index.Optional, | |
| 215 | unused: Unused = .zero, | |
| 216 | }; | |
| 217 | ||
| 218 | pub const StmtBlock = struct { | |
| 219 | label: u32, | |
| 220 | content: ExtraIndex(IndexList), | |
| 221 | }; | |
| 222 | ||
| 223 | pub fn deinit(tree: *JsTree, gpa: std.mem.Allocator) void { | |
| 224 | tree.nodes.deinit(gpa); | |
| 225 | gpa.free(tree.decl_deps); | |
| 226 | gpa.free(tree.extern_symbols); | |
| 227 | tree.extra.deinit(gpa); | |
| 228 | } | |
| 229 | ||
| 230 | pub fn nodeTag(tree: JsTree, index: Node.Index) Tag { | |
| 231 | return tree.nodes.items(.tag)[@intFromEnum(index)]; | |
| 232 | } | |
| 233 | ||
| 234 | pub fn nodeData(tree: JsTree, comptime tag: Tag, index: Node.Index) tag.Data() { | |
| 235 | return tree.nodes.items(.data)[@intFromEnum(index)].decode(tag, tree.nodeTag(index)); | |
| 236 | } | |
| 237 | ||
| 238 | pub fn mainStatements(tree: *const JsTree) []const Node.Index { | |
| 239 | return tree.nodeData(.root, .root).slice(tree); | |
| 240 | } | |
| 241 | ||
| 242 | const JsTree = @This(); | |
| 243 | ||
| 244 | const std = @import("std"); | |
| 245 | const assert = std.debug.assert; | |
| 246 | ||
| 247 | const InternPool = @import("InternPool.zig"); | |
| 248 | const Value = InternPool.Value; | |
| 249 | ||
| 250 | const NullTerminatedString = InternPool.NullTerminatedString; | |
| 251 | const String = InternPool.String; | |
| 252 | ||
| 253 | const Comp = @import("Comp.zig"); | |
| 254 | const Decl = Comp.Decl; | |
| 255 | ||
| 256 | const dod = @import("array.zig"); | |
| 257 | ||
| 258 | const Unused = @import("Ast.zig").Unused; |
src/core/Parse.zig+213-34| ... | ... | @@ -103,14 +103,18 @@ pub const ExpectedContext = enum { |
| 103 | 103 | statement, |
| 104 | 104 | expression, |
| 105 | 105 | else_clause, |
| 106 | struct_field, | |
| 107 | param_list, | |
| 106 | 108 | |
| 107 | 109 | pub fn description(tag: ExpectedContext) []const u8 { |
| 108 | 110 | return switch (tag) { |
| 109 | 111 | .param => "a parameter", |
| 112 | .param_list => "function parameters", | |
| 110 | 113 | .argument => "an argument", |
| 111 | 114 | .statement => "a statement", |
| 112 | 115 | .expression => "an expression", |
| 113 | 116 | .else_clause => "an else clause", |
| 117 | .struct_field => "a field definition", | |
| 114 | 118 | }; |
| 115 | 119 | } |
| 116 | 120 | }; |
| ... | ... | @@ -233,6 +237,8 @@ fn parseModuleMembers(p: *Parse) Error!SubList { |
| 233 | 237 | => try p.scratch.append(p.gpa, try p.parseModuleDecl()), |
| 234 | 238 | |
| 235 | 239 | .kw_struct, |
| 240 | => try p.scratch.append(p.gpa, try p.parseStructDecl(.declaration)), | |
| 241 | ||
| 236 | 242 | .kw_enum, |
| 237 | 243 | .kw_trait, |
| 238 | 244 | .kw_impl, |
| ... | ... | @@ -247,7 +253,7 @@ fn parseModuleMembers(p: *Parse) Error!SubList { |
| 247 | 253 | |
| 248 | 254 | const first_tag = p.currentTag(); |
| 249 | 255 | |
| 250 | try p.scratch.append(p.gpa, try p.parseExpression(true)); | |
| 256 | try p.scratch.append(p.gpa, try p.parseExpression(.statement)); | |
| 251 | 257 | |
| 252 | 258 | switch (first_tag) { |
| 253 | 259 | // some control flow and blocks are allowed to not have |
| ... | ... | @@ -280,6 +286,14 @@ fn parseModuleDecl(p: *Parse) Error!NodeIndex { |
| 280 | 286 | }; |
| 281 | 287 | } |
| 282 | 288 | |
| 289 | /// Some constructs are valid expressions as well as valid top-level | |
| 290 | /// declarations. Top level declarations must have associated names, but the | |
| 291 | /// anonymous versions of these must not have names. | |
| 292 | const ExprParseContext = enum { | |
| 293 | declaration, | |
| 294 | expression, | |
| 295 | }; | |
| 296 | ||
| 283 | 297 | fn parseFnDecl(p: *Parse) Error!NodeIndex { |
| 284 | 298 | const trace = t.trace(@src()); |
| 285 | 299 | defer trace.end(); |
| ... | ... | @@ -290,20 +304,20 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex { |
| 290 | 304 | |
| 291 | 305 | _ = try p.expect(.start_paren); |
| 292 | 306 | |
| 293 | const params = try p.parseParamList(); | |
| 307 | const params = try p.parseParamList(.types_required); | |
| 294 | 308 | |
| 295 | 309 | const return_type = if (p.eatToken(.minus_lt)) |_| |
| 296 | 310 | if (!p.isExpressionStart()) |
| 297 | 311 | return p.failUnexpectedToken(.expression) |
| 298 | 312 | else |
| 299 | try p.parseExpression(false) | |
| 313 | try p.parseExpression(.preceding_block) | |
| 300 | 314 | else |
| 301 | 315 | null; |
| 302 | 316 | |
| 303 | 317 | try p.expectWithoutAdvance(.start_brace); |
| 304 | 318 | const body = try p.parseBlock(); |
| 305 | 319 | |
| 306 | const extra = try p.addExtra(Ast.FnDecl, .{ | |
| 320 | const extra = try p.addExtra(Ast.Fn, .{ | |
| 307 | 321 | .params = params, |
| 308 | 322 | .return_type = optionalNodeIndex(return_type), |
| 309 | 323 | }); |
| ... | ... | @@ -311,14 +325,16 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex { |
| 311 | 325 | return try p.addNode(.{ |
| 312 | 326 | .tag = .fn_decl, |
| 313 | 327 | .main_token = main_token, |
| 314 | .data = .{ .fn_decl = .{ | |
| 328 | .data = .{ .function = .{ | |
| 315 | 329 | .body = body, |
| 316 | 330 | .extra = extra, |
| 317 | 331 | } }, |
| 318 | 332 | }); |
| 319 | 333 | } |
| 320 | 334 | |
| 321 | fn parseParamList(p: *Parse) Error!SubList { | |
| 335 | const TypePresence = enum { types_optional, types_required }; | |
| 336 | ||
| 337 | fn parseParamList(p: *Parse, types: TypePresence) Error!SubList { | |
| 322 | 338 | const trace = t.trace(@src()); |
| 323 | 339 | defer trace.end(); |
| 324 | 340 | |
| ... | ... | @@ -331,7 +347,7 @@ fn parseParamList(p: *Parse) Error!SubList { |
| 331 | 347 | |
| 332 | 348 | if (p.currentTag() == .end_paren) break; |
| 333 | 349 | |
| 334 | try p.scratch.append(p.gpa, try p.parseParamDecl()); | |
| 350 | try p.scratch.append(p.gpa, try p.parseParamDecl(types)); | |
| 335 | 351 | |
| 336 | 352 | switch (p.currentTag()) { |
| 337 | 353 | .comma => { |
| ... | ... | @@ -347,25 +363,92 @@ fn parseParamList(p: *Parse) Error!SubList { |
| 347 | 363 | return p.scratchToSubList(scratch_top); |
| 348 | 364 | } |
| 349 | 365 | |
| 350 | fn parseParamDecl(p: *Parse) Error!NodeIndex { | |
| 366 | fn parseParamDecl(p: *Parse, types: TypePresence) Error!NodeIndex { | |
| 351 | 367 | const trace = t.trace(@src()); |
| 352 | 368 | defer trace.end(); |
| 353 | 369 | |
| 354 | 370 | const main_token = p.assertToken(.id); |
| 355 | 371 | |
| 356 | _ = try p.expect(.colon); | |
| 357 | ||
| 358 | const ty = try p.parseExpression(false); | |
| 372 | const ty = if (types == .types_required or p.currentTag() == .colon) brk: { | |
| 373 | _ = try p.expect(.colon); | |
| 374 | break :brk try p.parseExpression(.expression); | |
| 375 | } else null; | |
| 359 | 376 | |
| 360 | 377 | return try p.addNode(.{ |
| 361 | 378 | .tag = .param_decl, |
| 362 | 379 | .main_token = main_token, |
| 363 | 380 | .data = .{ .param_decl = .{ |
| 364 | .type = ty, | |
| 381 | .type = optionalNodeIndex(ty), | |
| 365 | 382 | } }, |
| 366 | 383 | }); |
| 367 | 384 | } |
| 368 | 385 | |
| 386 | fn parseStructDecl(p: *Parse, ctx: ExprParseContext) Error!NodeIndex { | |
| 387 | const trace = t.trace(@src()); | |
| 388 | defer trace.end(); | |
| 389 | ||
| 390 | const main_token = p.assertToken(.kw_struct); | |
| 391 | ||
| 392 | _ = ctx; // TODO: | |
| 393 | _ = try p.expect(.id); | |
| 394 | ||
| 395 | _ = try p.expect(.start_brace); | |
| 396 | ||
| 397 | const fields = try p.parseStructFieldList(); | |
| 398 | ||
| 399 | return try p.addNode(.{ | |
| 400 | .tag = .struct_decl, | |
| 401 | .main_token = main_token, | |
| 402 | .data = .{ .list = fields }, | |
| 403 | }); | |
| 404 | } | |
| 405 | ||
| 406 | fn parseStructFieldList(p: *Parse) Error!SubList { | |
| 407 | const trace = t.trace(@src()); | |
| 408 | defer trace.end(); | |
| 409 | ||
| 410 | const scratch_top = p.scratch.items.len; | |
| 411 | defer p.scratch.shrinkRetainingCapacity(scratch_top); | |
| 412 | ||
| 413 | while (true) { | |
| 414 | const trace_param = t.traceFmt(@src(), "Param {d}", .{p.scratch.items.len - scratch_top}); | |
| 415 | defer trace_param.end(); | |
| 416 | ||
| 417 | if (p.currentTag() == .end_brace) break; | |
| 418 | ||
| 419 | try p.scratch.append(p.gpa, try p.parseStructField()); | |
| 420 | ||
| 421 | switch (p.currentTag()) { | |
| 422 | .comma => { | |
| 423 | _ = p.nextToken(); | |
| 424 | }, | |
| 425 | .end_brace => break, | |
| 426 | else => return p.failUnexpectedToken(.struct_field), | |
| 427 | } | |
| 428 | } | |
| 429 | ||
| 430 | _ = p.assertToken(.end_brace); | |
| 431 | ||
| 432 | return p.scratchToSubList(scratch_top); | |
| 433 | } | |
| 434 | ||
| 435 | fn parseStructField(p: *Parse) Error!NodeIndex { | |
| 436 | const trace = t.trace(@src()); | |
| 437 | defer trace.end(); | |
| 438 | ||
| 439 | const main_token = try p.expect(.id); | |
| 440 | ||
| 441 | _ = try p.expect(.colon); | |
| 442 | ||
| 443 | const expr = try p.parseExpression(.expression); | |
| 444 | ||
| 445 | return try p.addNode(.{ | |
| 446 | .tag = .struct_field, | |
| 447 | .main_token = main_token, | |
| 448 | .data = .{ .un = .{ .expr = expr } }, | |
| 449 | }); | |
| 450 | } | |
| 451 | ||
| 369 | 452 | fn parseBlock(p: *Parse) Error!NodeIndex { |
| 370 | 453 | const trace = t.trace(@src()); |
| 371 | 454 | defer trace.end(); |
| ... | ... | @@ -390,7 +473,8 @@ fn parseLabelExpression(p: *Parse) Error!NodeIndex { |
| 390 | 473 | |
| 391 | 474 | const main_token = p.assertToken(.id); |
| 392 | 475 | _ = try p.expect(.colon); |
| 393 | const expr = try p.parseExpression(false); | |
| 476 | const expr = try p.parseExpression(.expression); | |
| 477 | ||
| 394 | 478 | return try p.addNode(.{ |
| 395 | 479 | .tag = .label, |
| 396 | 480 | .main_token = main_token, |
| ... | ... | @@ -422,7 +506,7 @@ fn parseBlockContents(p: *Parse) Error!struct { Node.Tag, SubList } { |
| 422 | 506 | |
| 423 | 507 | const first_tag = p.currentTag(); |
| 424 | 508 | |
| 425 | try p.scratch.append(p.gpa, try p.parseExpression(true)); | |
| 509 | try p.scratch.append(p.gpa, try p.parseExpression(.statement)); | |
| 426 | 510 | |
| 427 | 511 | switch (first_tag) { |
| 428 | 512 | // some control flow and blocks are allowed to not have |
| ... | ... | @@ -466,13 +550,13 @@ fn parseVarDecl(p: *Parse) Error!NodeIndex { |
| 466 | 550 | _ = try p.expect(.id); |
| 467 | 551 | |
| 468 | 552 | const data_type: OptionalNodeIndex = if (p.eatToken(.colon)) |_| |
| 469 | optionalNodeIndex(try p.tryParseExpression(false)) | |
| 553 | optionalNodeIndex(try p.tryParseExpression(.expression)) | |
| 470 | 554 | else |
| 471 | 555 | .none; |
| 472 | 556 | |
| 473 | 557 | _ = try p.expect(.equal); |
| 474 | 558 | |
| 475 | const data_init = try p.tryParseExpression(true); | |
| 559 | const data_init = try p.tryParseExpression(.expression); | |
| 476 | 560 | |
| 477 | 561 | _ = try p.expect(.semi); |
| 478 | 562 | |
| ... | ... | @@ -490,6 +574,7 @@ fn isExpressionStart(p: *Parse) bool { |
| 490 | 574 | return switch (p.currentTag()) { |
| 491 | 575 | .bang, |
| 492 | 576 | .builtin_id, |
| 577 | .dot, | |
| 493 | 578 | .id, |
| 494 | 579 | .kw_assert, |
| 495 | 580 | .kw_break, |
| ... | ... | @@ -504,6 +589,7 @@ fn isExpressionStart(p: *Parse) bool { |
| 504 | 589 | .start_paren, |
| 505 | 590 | .string, |
| 506 | 591 | .tilde, |
| 592 | .kw_fn, | |
| 507 | 593 | => true, |
| 508 | 594 | else => false, |
| 509 | 595 | }; |
| ... | ... | @@ -692,7 +778,17 @@ const Operator = enum(u8) { |
| 692 | 778 | } |
| 693 | 779 | }; |
| 694 | 780 | |
| 695 | fn parseExpression(p: *Parse, allow_assign_operators: bool) Error!NodeIndex { | |
| 781 | const ParseExprMode = enum { | |
| 782 | /// Standard parsing | |
| 783 | expression, | |
| 784 | /// Statements allow assignment binary operators in addition to other expressions. | |
| 785 | statement, | |
| 786 | /// This bans struct initializations so that parsing stops at a '{' | |
| 787 | /// Example: "if hello {" must not be parsed as a struct initialization. | |
| 788 | preceding_block, | |
| 789 | }; | |
| 790 | ||
| 791 | fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { | |
| 696 | 792 | const trace = t.trace(@src()); |
| 697 | 793 | defer trace.end(); |
| 698 | 794 | |
| ... | ... | @@ -711,24 +807,24 @@ fn parseExpression(p: *Parse, allow_assign_operators: bool) Error!NodeIndex { |
| 711 | 807 | else => {}, |
| 712 | 808 | } |
| 713 | 809 | |
| 714 | var expr = try p.parseExpressionPart(); | |
| 810 | var expr = try p.parseExpressionPart(mode); | |
| 715 | 811 | |
| 716 | 812 | while (Operator.fromToken(p.currentTag())) |op| { |
| 717 | if (!allow_assign_operators and op.isAssign()) break; | |
| 718 | expr = try p.parseBinarySuffix(expr, op, allow_assign_operators); | |
| 813 | if (mode != .statement and op.isAssign()) break; | |
| 814 | expr = try p.parseBinarySuffix(expr, op, mode); | |
| 719 | 815 | } |
| 720 | 816 | |
| 721 | 817 | return expr; |
| 722 | 818 | } |
| 723 | 819 | |
| 724 | fn tryParseExpression(p: *Parse, allow_assign_operators: bool) Error!NodeIndex { | |
| 820 | fn tryParseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex { | |
| 725 | 821 | return if (p.isExpressionStart()) |
| 726 | p.parseExpression(allow_assign_operators) | |
| 822 | p.parseExpression(mode) | |
| 727 | 823 | else |
| 728 | 824 | p.failUnexpectedToken(.expression); |
| 729 | 825 | } |
| 730 | 826 | |
| 731 | fn parseExpressionPart(p: *Parse) Error!NodeIndex { | |
| 827 | fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { | |
| 732 | 828 | const trace = t.trace(@src()); |
| 733 | 829 | defer trace.end(); |
| 734 | 830 | |
| ... | ... | @@ -749,6 +845,8 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex { |
| 749 | 845 | .kw_if => try p.parseIf(), |
| 750 | 846 | .start_paren => try p.parseGroup(), |
| 751 | 847 | .start_brace => try p.parseBlock(), |
| 848 | .dot => try p.parseAnonInstantiation(), | |
| 849 | .kw_fn => try p.parseAnonFunction(), | |
| 752 | 850 | |
| 753 | 851 | // Unary operators |
| 754 | 852 | inline .bang, |
| ... | ... | @@ -759,7 +857,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex { |
| 759 | 857 | .kw_assert, |
| 760 | 858 | => |tag| unary: { |
| 761 | 859 | const main_token = p.nextToken(); |
| 762 | const expr = try p.parseExpressionPart(); | |
| 860 | const expr = try p.parseExpressionPart(mode); | |
| 763 | 861 | |
| 764 | 862 | break :unary try p.addNode(.{ |
| 765 | 863 | .tag = switch (tag) { |
| ... | ... | @@ -819,7 +917,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex { |
| 819 | 917 | }, |
| 820 | 918 | .start_bracket => { |
| 821 | 919 | const main_token = p.nextToken(); |
| 822 | const index = try p.parseExpression(false); | |
| 920 | const index = try p.parseExpression(.expression); | |
| 823 | 921 | |
| 824 | 922 | expr = try p.addNode(.{ |
| 825 | 923 | .tag = .index, |
| ... | ... | @@ -830,7 +928,20 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex { |
| 830 | 928 | } }, |
| 831 | 929 | }); |
| 832 | 930 | }, |
| 931 | .start_brace => { | |
| 932 | if (mode == .preceding_block) break; | |
| 833 | 933 | |
| 934 | const main_token = p.nextToken(); | |
| 935 | const initializers = try p.parseStructFieldList(); | |
| 936 | expr = try p.addNode(.{ | |
| 937 | .tag = .struct_init, | |
| 938 | .main_token = main_token, | |
| 939 | .data = .{ .struct_init = .{ | |
| 940 | .base = optionalNodeIndex(expr), | |
| 941 | .extra = try p.addExtra(Ast.SubList, initializers), | |
| 942 | } }, | |
| 943 | }); | |
| 944 | }, | |
| 834 | 945 | else => break, |
| 835 | 946 | } |
| 836 | 947 | } |
| ... | ... | @@ -838,16 +949,16 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex { |
| 838 | 949 | return expr; |
| 839 | 950 | } |
| 840 | 951 | |
| 841 | fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, allow_assign_operators: bool) Error!NodeIndex { | |
| 952 | fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, mode: ParseExprMode) Error!NodeIndex { | |
| 842 | 953 | const trace = t.trace(@src()); |
| 843 | 954 | defer trace.end(); |
| 844 | 955 | |
| 845 | 956 | const main_token = p.nextToken(); |
| 846 | 957 | const level = op.level(); |
| 847 | var rhs = try p.parseExpressionPart(); | |
| 958 | var rhs = try p.parseExpressionPart(mode); | |
| 848 | 959 | |
| 849 | 960 | while (Operator.fromToken(p.currentTag())) |next_op| { |
| 850 | if (!allow_assign_operators and next_op.isAssign()) break; | |
| 961 | if (mode != .statement and next_op.isAssign()) break; | |
| 851 | 962 | |
| 852 | 963 | const new_level = next_op.level(); |
| 853 | 964 | const use = switch (op.direction()) { |
| ... | ... | @@ -856,7 +967,7 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, allow_assign_opera |
| 856 | 967 | }; |
| 857 | 968 | if (!use) break; |
| 858 | 969 | |
| 859 | rhs = try p.parseBinarySuffix(rhs, next_op, allow_assign_operators); | |
| 970 | rhs = try p.parseBinarySuffix(rhs, next_op, mode); | |
| 860 | 971 | } |
| 861 | 972 | |
| 862 | 973 | return try p.addNode(.{ |
| ... | ... | @@ -876,7 +987,7 @@ fn parseReturn(p: *Parse) Error!NodeIndex { |
| 876 | 987 | const main_token = p.assertToken(.kw_return); |
| 877 | 988 | |
| 878 | 989 | const expr = if (p.isExpressionStart()) |
| 879 | try p.parseExpression(false) | |
| 990 | try p.parseExpression(.expression) | |
| 880 | 991 | else |
| 881 | 992 | null; |
| 882 | 993 | |
| ... | ... | @@ -901,7 +1012,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex { |
| 901 | 1012 | null; |
| 902 | 1013 | |
| 903 | 1014 | const expr = if (p.isExpressionStart()) |
| 904 | try p.parseExpression(false) | |
| 1015 | try p.parseExpression(.expression) | |
| 905 | 1016 | else |
| 906 | 1017 | null; |
| 907 | 1018 | |
| ... | ... | @@ -940,7 +1051,7 @@ fn parseCallArgs(p: *Parse) Error!SubList { |
| 940 | 1051 | defer p.scratch.shrinkRetainingCapacity(scratch_top); |
| 941 | 1052 | |
| 942 | 1053 | while (p.isExpressionStart()) { |
| 943 | try p.scratch.append(p.gpa, try p.parseExpression(false)); | |
| 1054 | try p.scratch.append(p.gpa, try p.parseExpression(.expression)); | |
| 944 | 1055 | |
| 945 | 1056 | switch (p.currentTag()) { |
| 946 | 1057 | .end_paren => { |
| ... | ... | @@ -966,7 +1077,7 @@ fn parseIf(p: *Parse) Error!NodeIndex { |
| 966 | 1077 | |
| 967 | 1078 | const main_token = p.assertToken(.kw_if); |
| 968 | 1079 | |
| 969 | const cond = try p.parseExpression(false); | |
| 1080 | const cond = try p.parseExpression(.preceding_block); | |
| 970 | 1081 | |
| 971 | 1082 | _ = try p.expectWithoutAdvance(.start_brace); |
| 972 | 1083 | const yes = try p.parseBlock(); |
| ... | ... | @@ -1012,7 +1123,7 @@ fn parseGroup(p: *Parse) Error!NodeIndex { |
| 1012 | 1123 | |
| 1013 | 1124 | const main_token = p.assertToken(.start_paren); |
| 1014 | 1125 | |
| 1015 | const expr = try p.parseExpression(false); | |
| 1126 | const expr = try p.parseExpression(.expression); | |
| 1016 | 1127 | |
| 1017 | 1128 | _ = try p.expect(.end_paren); |
| 1018 | 1129 | |
| ... | ... | @@ -1025,6 +1136,74 @@ fn parseGroup(p: *Parse) Error!NodeIndex { |
| 1025 | 1136 | }); |
| 1026 | 1137 | } |
| 1027 | 1138 | |
| 1139 | fn parseAnonInstantiation(p: *Parse) Error!NodeIndex { | |
| 1140 | const trace = t.trace(@src()); | |
| 1141 | defer trace.end(); | |
| 1142 | ||
| 1143 | _ = p.assertToken(.dot); | |
| 1144 | ||
| 1145 | const main_token = try p.expect(.start_brace); | |
| 1146 | const initializers = try p.parseStructFieldList(); | |
| 1147 | return try p.addNode(.{ | |
| 1148 | .tag = .struct_init, | |
| 1149 | .main_token = main_token, | |
| 1150 | .data = .{ .struct_init = .{ | |
| 1151 | .base = .none, | |
| 1152 | .extra = try p.addExtra(Ast.SubList, initializers), | |
| 1153 | } }, | |
| 1154 | }); | |
| 1155 | } | |
| 1156 | ||
| 1157 | fn parseAnonFunction(p: *Parse) Error!NodeIndex { | |
| 1158 | const trace = t.trace(@src()); | |
| 1159 | defer trace.end(); | |
| 1160 | ||
| 1161 | const main_token = p.assertToken(.kw_fn); | |
| 1162 | ||
| 1163 | const params = switch (p.currentTag()) { | |
| 1164 | // fn { 1 } | |
| 1165 | .start_brace => SubList.empty, | |
| 1166 | // fn (args, here) { 2 } | |
| 1167 | .start_paren => param_list: { | |
| 1168 | _ = p.assertToken(.start_paren); | |
| 1169 | break :param_list try p.parseParamList(.types_optional); | |
| 1170 | }, | |
| 1171 | // fn arg { 3 } | |
| 1172 | .id => single_param: { | |
| 1173 | const scratch_top = p.scratch.items.len; | |
| 1174 | defer p.scratch.shrinkRetainingCapacity(scratch_top); | |
| 1175 | try p.scratch.append(p.gpa, try p.blankNode(.id)); | |
| 1176 | break :single_param try p.scratchToSubList(scratch_top); | |
| 1177 | }, | |
| 1178 | else => return p.failUnexpectedToken(.param_list), | |
| 1179 | }; | |
| 1180 | ||
| 1181 | const return_type = if (p.eatToken(.minus_lt)) |_| | |
| 1182 | if (!p.isExpressionStart()) | |
| 1183 | return p.failUnexpectedToken(.expression) | |
| 1184 | else | |
| 1185 | try p.parseExpression(.preceding_block) | |
| 1186 | else | |
| 1187 | null; | |
| 1188 | ||
| 1189 | try p.expectWithoutAdvance(.start_brace); | |
| 1190 | const body = try p.parseBlock(); | |
| 1191 | ||
| 1192 | const extra = try p.addExtra(Ast.Fn, .{ | |
| 1193 | .params = params, | |
| 1194 | .return_type = optionalNodeIndex(return_type), | |
| 1195 | }); | |
| 1196 | ||
| 1197 | return try p.addNode(.{ | |
| 1198 | .tag = .fn_expr, | |
| 1199 | .main_token = main_token, | |
| 1200 | .data = .{ .function = .{ | |
| 1201 | .extra = extra, | |
| 1202 | .body = body, | |
| 1203 | } }, | |
| 1204 | }); | |
| 1205 | } | |
| 1206 | ||
| 1028 | 1207 | // helpers |
| 1029 | 1208 | |
| 1030 | 1209 | /// Append a node and return its index. |
src/core/Report.zig+139-17| ... | ... | @@ -174,8 +174,8 @@ pub const Err = union(enum) { |
| 174 | 174 | fn build(data: @This(), ctx: *MsgContext) !void { |
| 175 | 175 | const node = try data.src.get(ctx); |
| 176 | 176 | try ctx.msgNode(node, "expected \"{s}\", got \"{s}\"", .{ |
| 177 | data.to.displayName(&ctx.comp.intern_pool), | |
| 178 | data.from.displayName(&ctx.comp.intern_pool), | |
| 177 | data.to.displayName(ctx.ip()), | |
| 178 | data.from.displayName(ctx.ip()), | |
| 179 | 179 | }); |
| 180 | 180 | } |
| 181 | 181 | }, |
| ... | ... | @@ -186,7 +186,7 @@ pub const Err = union(enum) { |
| 186 | 186 | fn build(data: @This(), ctx: *MsgContext) !void { |
| 187 | 187 | const node = try data.call.get(ctx); |
| 188 | 188 | try ctx.msgNode(node, "\"{s}\" is not callable", .{ |
| 189 | data.ty.displayName(&ctx.comp.intern_pool), | |
| 189 | data.ty.displayName(ctx.ip()), | |
| 190 | 190 | }); |
| 191 | 191 | } |
| 192 | 192 | }, |
| ... | ... | @@ -210,7 +210,7 @@ pub const Err = union(enum) { |
| 210 | 210 | fn build(data: @This(), ctx: *MsgContext) !void { |
| 211 | 211 | const node = try data.src.get(ctx); |
| 212 | 212 | try ctx.msgNode(node, "type \"{}\" cannot be represented as a runtime value", .{ |
| 213 | data.type.displayName(&ctx.comp.intern_pool), | |
| 213 | data.type.displayName(ctx.ip()), | |
| 214 | 214 | }); |
| 215 | 215 | } |
| 216 | 216 | }, |
| ... | ... | @@ -221,7 +221,7 @@ pub const Err = union(enum) { |
| 221 | 221 | fn build(data: @This(), ctx: *MsgContext) !void { |
| 222 | 222 | const node = try data.src.get(ctx); |
| 223 | 223 | try ctx.msgNode(node, "parameter of comptime type \"{s}\" must be marked \"comptime\"", .{ |
| 224 | data.type.displayName(&ctx.comp.intern_pool), | |
| 224 | data.type.displayName(ctx.ip()), | |
| 225 | 225 | }); |
| 226 | 226 | } |
| 227 | 227 | }, |
| ... | ... | @@ -275,8 +275,24 @@ pub const Err = union(enum) { |
| 275 | 275 | "\"{s}\" operator is not compatible with types \"{}\" and \"{}\"", |
| 276 | 276 | .{ |
| 277 | 277 | ctx.ast.tokenSlice(ctx.ast.nodeToken(node)), |
| 278 | data.lhs.displayName(&ctx.comp.intern_pool), | |
| 279 | data.rhs.displayName(&ctx.comp.intern_pool), | |
| 278 | data.lhs.displayName(ctx.ip()), | |
| 279 | data.rhs.displayName(ctx.ip()), | |
| 280 | }, | |
| 281 | ); | |
| 282 | } | |
| 283 | }, | |
| 284 | un_op_mismatch: struct { | |
| 285 | src: LazyNode, | |
| 286 | ty: Type, | |
| 287 | ||
| 288 | fn build(data: @This(), ctx: *MsgContext) !void { | |
| 289 | const node = try data.src.get(ctx); | |
| 290 | try ctx.msgNode( | |
| 291 | node, | |
| 292 | "\"{s}\" operator is not compatible with type \"{}\"", | |
| 293 | .{ | |
| 294 | ctx.ast.tokenSlice(ctx.ast.nodeToken(node)), | |
| 295 | data.ty.displayName(ctx.ip()), | |
| 280 | 296 | }, |
| 281 | 297 | ); |
| 282 | 298 | } |
| ... | ... | @@ -293,6 +309,96 @@ pub const Err = union(enum) { |
| 293 | 309 | ); |
| 294 | 310 | } |
| 295 | 311 | }, |
| 312 | struct_init_invalid_type: struct { | |
| 313 | ty: Type, | |
| 314 | src: LazyNode, | |
| 315 | ||
| 316 | fn build(data: @This(), ctx: *MsgContext) !void { | |
| 317 | const node = try data.src.get(ctx); | |
| 318 | try ctx.msgNode( | |
| 319 | node, | |
| 320 | "\"{}\" is not constructable via struct initialization syntax", | |
| 321 | .{data.ty.displayName(ctx.ip())}, | |
| 322 | ); | |
| 323 | } | |
| 324 | }, | |
| 325 | struct_init_unknown_field: struct { | |
| 326 | ty: Type, | |
| 327 | src: LazyNode, | |
| 328 | ||
| 329 | fn build(data: @This(), ctx: *MsgContext) !void { | |
| 330 | const node = try data.src.get(ctx); | |
| 331 | try ctx.msgNode( | |
| 332 | node, | |
| 333 | "\"{}\" does not have field {}", | |
| 334 | .{ data.ty.displayName(ctx.ip()), formatId("field") }, | |
| 335 | ); | |
| 336 | } | |
| 337 | }, | |
| 338 | struct_init_duplicate_field: struct { | |
| 339 | ty: Type, | |
| 340 | src: LazyNode, | |
| 341 | ||
| 342 | fn build(data: @This(), ctx: *MsgContext) !void { | |
| 343 | const node = try data.src.get(ctx); | |
| 344 | try ctx.msgNode( | |
| 345 | node, | |
| 346 | "duplicate field {}", | |
| 347 | .{formatId("field")}, | |
| 348 | ); | |
| 349 | } | |
| 350 | }, | |
| 351 | struct_init_missing_field: struct { | |
| 352 | ty: Type, | |
| 353 | src: LazyNode, | |
| 354 | field: u32, | |
| 355 | ||
| 356 | fn build(data: @This(), ctx: *MsgContext) !void { | |
| 357 | const node = try data.src.get(ctx); | |
| 358 | try ctx.msgNode( | |
| 359 | node, | |
| 360 | "{} is missing field {}", | |
| 361 | .{ | |
| 362 | data.ty.displayName(ctx.ip()), | |
| 363 | formatId( | |
| 364 | data.ty.toStructType(ctx.ip()).? | |
| 365 | .field_keys[data.field].toSlice(ctx.ip()), | |
| 366 | ), | |
| 367 | }, | |
| 368 | ); | |
| 369 | } | |
| 370 | }, | |
| 371 | ||
| 372 | field_lookup_failed: struct { | |
| 373 | ty: Type, | |
| 374 | src: LazyNode, | |
| 375 | field: Value, | |
| 376 | ||
| 377 | fn build(data: @This(), ctx: *MsgContext) !void { | |
| 378 | const node = try data.src.get(ctx); | |
| 379 | try ctx.msgNode( | |
| 380 | node, | |
| 381 | "{} is not a field on \"{}\"", | |
| 382 | .{ | |
| 383 | formatId(data.field.asString(ctx.ip()).toSlice(ctx.ip())), | |
| 384 | data.ty.displayName(ctx.ip()), | |
| 385 | }, | |
| 386 | ); | |
| 387 | } | |
| 388 | }, | |
| 389 | field_invalid_type: struct { | |
| 390 | ty: Type, | |
| 391 | src: LazyNode, | |
| 392 | ||
| 393 | fn build(data: @This(), ctx: *MsgContext) !void { | |
| 394 | const node = try data.src.get(ctx); | |
| 395 | try ctx.msgNode( | |
| 396 | node, | |
| 397 | "\"{}\" does not support field access", | |
| 398 | .{data.ty.displayName(ctx.ip())}, | |
| 399 | ); | |
| 400 | } | |
| 401 | }, | |
| 296 | 402 | }; |
| 297 | 403 | |
| 298 | 404 | pub const Warn = union(enum) { |
| ... | ... | @@ -783,8 +889,8 @@ pub const Highlight = union(enum) { |
| 783 | 889 | |
| 784 | 890 | /// This serves as a small API to make creating messages easier. |
| 785 | 891 | pub const MsgContext = struct { |
| 786 | file: *File, | |
| 787 | comp: *Comp, | |
| 892 | file: *const File, | |
| 893 | comp: *const Comp, | |
| 788 | 894 | ast: *const Ast, |
| 789 | 895 | report: *const Report, |
| 790 | 896 | utf8_source: [:0]const u8, |
| ... | ... | @@ -819,6 +925,10 @@ pub const MsgContext = struct { |
| 819 | 925 | pub fn msgAlone(m: *MsgContext, comptime fmt: []const u8, args: anytype) !void { |
| 820 | 926 | return m.msg(.alone, fmt, args); |
| 821 | 927 | } |
| 928 | ||
| 929 | pub fn ip(ctx: *const MsgContext) *const InternPool { | |
| 930 | return &ctx.comp.intern_pool; | |
| 931 | } | |
| 822 | 932 | }; |
| 823 | 933 | |
| 824 | 934 | pub const LazyNode = struct { |
| ... | ... | @@ -828,7 +938,7 @@ pub const LazyNode = struct { |
| 828 | 938 | |
| 829 | 939 | pub fn get(lazy: LazyNode, ctx: *MsgContext) !NodeIndex { |
| 830 | 940 | const decl = ctx.comp.intern_pool.declPtrConst(lazy.decl); |
| 831 | const ns = ctx.comp.intern_pool.namespacePtr(decl.src_namespace); | |
| 941 | const ns = ctx.comp.intern_pool.namespacePtrConst(decl.src_namespace); | |
| 832 | 942 | |
| 833 | 943 | // TODO: This might change if we have errors like |
| 834 | 944 | // - function arguments not matching at <callsite> |
| ... | ... | @@ -845,10 +955,9 @@ pub const LazyNode = struct { |
| 845 | 955 | // this will be required once incremental compilation hits |
| 846 | 956 | @panic("TODO: teg was unloaded")); |
| 847 | 957 | |
| 848 | const decl_inst = decl.teg_decl_index.unwrap() orelse | |
| 849 | unreachable; // file was not analyzed, yet teg loaded? | |
| 958 | const decl_inst = decl.teg_decl_index; | |
| 850 | 959 | |
| 851 | const teg_decl = teg.instData(.declaration, decl_inst.resolve(&ctx.comp.intern_pool)); | |
| 960 | const teg_decl = teg.instData(.declaration, decl_inst.resolve(ctx.ip())); | |
| 852 | 961 | |
| 853 | 962 | const node_offset = teg.instNodeOffset(lazy.inst); |
| 854 | 963 | |
| ... | ... | @@ -903,7 +1012,7 @@ pub const LazyNode = struct { |
| 903 | 1012 | }, |
| 904 | 1013 | .declaration => { |
| 905 | 1014 | assert(node_tag == .fn_decl); |
| 906 | const fn_decl = ctx.ast.extraData(data.fn_decl.extra, Ast.FnDecl); | |
| 1015 | const fn_decl = ctx.ast.extraData(data.function.extra, Ast.Fn); | |
| 907 | 1016 | return switch (lazy.operand) { |
| 908 | 1017 | .return_type => fn_decl.return_type.unwrap().?, |
| 909 | 1018 | else => fn_decl.params.slice(ctx.ast)[lazy.operand.getArgId()], |
| ... | ... | @@ -912,11 +1021,24 @@ pub const LazyNode = struct { |
| 912 | 1021 | .js => { |
| 913 | 1022 | return base_node; |
| 914 | 1023 | }, |
| 1024 | .struct_init => { | |
| 1025 | assert(node_tag == .struct_init); | |
| 1026 | switch (lazy.operand) { | |
| 1027 | .operand => return data.struct_init.base.unwrap() orelse | |
| 1028 | // for anon struct initializations, this will point at the '.' | |
| 1029 | base_node, | |
| 1030 | else => { | |
| 1031 | const fields = ctx.ast | |
| 1032 | .extraData(data.struct_init.extra, Ast.SubList) | |
| 1033 | .slice(ctx.ast); | |
| 1034 | const field = fields[lazy.operand.getArgId()]; | |
| 1035 | return ctx.ast.nodeData(field).un.expr; | |
| 1036 | }, | |
| 1037 | } | |
| 1038 | }, | |
| 915 | 1039 | else => {}, |
| 916 | 1040 | } |
| 917 | 1041 | |
| 918 | if (!std.debug.runtime_safety) unreachable; | |
| 919 | ||
| 920 | 1042 | std.debug.panic("TODO: operand lookup: .{s} + .{s} + {}", .{ |
| 921 | 1043 | @tagName(teg.instTag(lazy.inst)), |
| 922 | 1044 | @tagName(ctx.ast.nodeTag(base_node)), |
| ... | ... | @@ -1020,7 +1142,7 @@ fn formatIdImpl( |
| 1020 | 1142 | // differences in the string formatters. Ideally these are merged |
| 1021 | 1143 | // under a common function that takes an argument of the 'mode' to |
| 1022 | 1144 | // print the string in. |
| 1023 | const JSLink = @import("JsLink.zig"); | |
| 1145 | const JSLink = @import("mod.zig").Js.Link; | |
| 1024 | 1146 | const indexOfNeedsEscape = JSLink.indexOfNeedsEscape; |
| 1025 | 1147 | const canPrintWithoutEscape = JSLink.canPrintWithoutEscape; |
| 1026 | 1148 |
src/core/Sema.zig+424-95| ... | ... | @@ -1,5 +1,28 @@ |
| 1 | 1 | /// absolutely stealing zig on this one haha |
| 2 | 2 | /// This struct is created per declaration and chills |
| 3 | /// | |
| 4 | /// TODO: explain this in more depth. or maybe take this to architecture.md | |
| 5 | /// | |
| 6 | /// All things in semantic analysis are either Air.Inst.Index (runtime known | |
| 7 | /// value pointing to the instruction that computed it), or Value (comptime known) | |
| 8 | /// | |
| 9 | /// There is a union of these two called Ref [Air.Inst.Ref]. Generally, | |
| 10 | /// functions referring to Ref refer to runtime known values. | |
| 11 | /// | |
| 12 | /// Many things have two versions, one for Ref (runtime) + one for Value (comptime) | |
| 13 | /// | |
| 14 | /// Example is going from Teg.Inst.Index to the result of it's analysis: | |
| 15 | /// - s.resolveRef() | |
| 16 | /// - s.resolveValue() | |
| 17 | /// The latter will ensure the result is a Value (comptime) and return that. | |
| 18 | /// | |
| 19 | /// Many things in Semantic Analysis don't have a way to represent constants | |
| 20 | /// using Air.Inst.Index, and we also want to avoid emitting things like `1 + 2`. | |
| 21 | /// | |
| 22 | /// We do that by using Air.Inst.Ref.value() or .decode(), which allows | |
| 23 | /// observing if the value is known at compile time. This makes analysis | |
| 24 | /// lazy and forces constant folding whenever possible. (see .bool_not for a | |
| 25 | /// simple example) | |
| 3 | 26 | |
| 4 | 27 | // inputs: |
| 5 | 28 | comp: *Comp, |
| ... | ... | @@ -16,12 +39,14 @@ inst_map: InstMap = undefined, |
| 16 | 39 | /// scratch memory. can be used in any place you might temporarily allocate an arraylist |
| 17 | 40 | /// this is most notably used to store block contents before the block is finalized |
| 18 | 41 | scratch: std.ArrayListUnmanaged(u32) = .{}, |
| 42 | ||
| 43 | /// This is only set for function declarations | |
| 19 | 44 | params: []Type = undefined, |
| 20 | 45 | |
| 21 | 46 | // outputs |
| 22 | 47 | report: Report.Builder = undefined, |
| 23 | 48 | air_instructions: std.MultiArrayList(Air.Inst) = .{}, |
| 24 | air_extra: dod.ExtraStore(.air, .@"var") = .{}, | |
| 49 | air_extra: store.ExtraStore(.air, .@"var") = .{}, | |
| 25 | 50 | |
| 26 | 51 | temp_result_map: std.AutoArrayHashMapUnmanaged(Teg.Inst.Index, Type) = .{}, |
| 27 | 52 | |
| ... | ... | @@ -41,7 +66,7 @@ pub const Error = error{ |
| 41 | 66 | OutOfMemory, |
| 42 | 67 | } || Comp.File.GetTegError; |
| 43 | 68 | |
| 44 | /// Instead of using a hash map for this, just pre-allocate a list long enough to hold everything. | |
| 69 | /// Instead of using a Hash map for this, just pre-allocate a list long enough to hold everything. | |
| 45 | 70 | /// This is fine because all instructions are going to be adjacent in a single declaration. |
| 46 | 71 | const InstMap = struct { |
| 47 | 72 | items: []Air.Inst.Ref.Optional, |
| ... | ... | @@ -70,10 +95,11 @@ const InstMap = struct { |
| 70 | 95 | } |
| 71 | 96 | |
| 72 | 97 | pub fn add(map: *InstMap, from: Teg.Inst.Index, to: Air.Inst.Ref) !void { |
| 73 | if (assertWithMessage(map.get(from) == null)) { | |
| 74 | std.debug.panic("%{d} is already mapped (to #{d})", .{ | |
| 98 | if (assertFailed(map.get(from) == null)) { | |
| 99 | const s: *const Sema = @fieldParentPtr("inst_map", map); | |
| 100 | std.debug.panic("%{d} is already mapped (to {})", .{ | |
| 75 | 101 | @intFromEnum(from), |
| 76 | @intFromEnum(map.get(from).?.decode().index), | |
| 102 | map.get(from).?.dump(s.ip), | |
| 77 | 103 | }); |
| 78 | 104 | } |
| 79 | 105 | const source_i = @intFromEnum(from); |
| ... | ... | @@ -91,12 +117,10 @@ pub fn init(s: *Sema) !void { |
| 91 | 117 | .current_file = s.ip.namespacePtr(s.namespace_index).file_scope, |
| 92 | 118 | }; |
| 93 | 119 | |
| 94 | try s.comp.current_compile_decls.append(s.gpa, s.decl_index); | |
| 95 | ||
| 96 | 120 | s.inst_map = try InstMap.init( |
| 97 | 121 | s.arena, |
| 98 | 122 | &s.code, |
| 99 | s.ip.declPtr(s.decl_index).teg_decl_index.unwrap().?.resolve(s.ip), | |
| 123 | s.ip.declPtr(s.decl_index).teg_decl_index.resolve(s.ip), | |
| 100 | 124 | ); |
| 101 | 125 | } |
| 102 | 126 | |
| ... | ... | @@ -117,21 +141,55 @@ pub fn deinit(s: *Sema) void { |
| 117 | 141 | } |
| 118 | 142 | |
| 119 | 143 | pub fn finalize(s: *Sema) !void { |
| 144 | s.decl.analysis = .complete; | |
| 145 | ||
| 120 | 146 | const air: Air = s.tempAir(); |
| 121 | 147 | |
| 122 | if (s.comp.options.dump_ir) { | |
| 123 | std.debug.print("=" ** 20, .{}); | |
| 124 | std.debug.print("\nAir for @ {d}\n", .{@intFromEnum(s.decl.teg_decl_index)}); | |
| 125 | air.dumpToWriter(s.ip, std.io.getStdErr().writer()) catch {}; | |
| 126 | } | |
| 148 | if (s.decl.value().valueTag(s.ip) == .func) { | |
| 149 | if (s.comp.options.dump_ir) { | |
| 150 | std.debug.print("=" ** 20, .{}); | |
| 151 | std.debug.print("\nAir for {}\n", .{s.decl.value().dump(s.ip)}); | |
| 152 | air.dumpToWriter(s.ip, std.io.getStdErr().writer()) catch {}; | |
| 153 | } | |
| 127 | 154 | |
| 128 | try s.comp.codeGenDecl(s.decl, &air); | |
| 155 | try s.comp.codeGenDecl(s.decl.value(), &air); | |
| 156 | } | |
| 129 | 157 | } |
| 130 | 158 | |
| 131 | 159 | // --- analyze functions --- |
| 132 | 160 | |
| 133 | 161 | /// This is the entrypoint to semantic analysis: It all starts at a declaration. |
| 134 | 162 | pub fn analyzeDeclaration(s: *Sema) Error!Value { |
| 163 | const decl = s.code.declData(s.inst_map.start); | |
| 164 | switch (decl.kind) { | |
| 165 | .function => return analyzeFunctionDeclaration(s), | |
| 166 | .immutable => {}, | |
| 167 | } | |
| 168 | ||
| 169 | const root_block = decl.body; | |
| 170 | switch (root_block.decode()) { | |
| 171 | .value => |val| { | |
| 172 | s.decl.raw_value = val; | |
| 173 | s.decl.analysis = .complete; | |
| 174 | }, | |
| 175 | .index => |index| { | |
| 176 | const result = try s.analyzeBlock(index); | |
| 177 | switch (result.decode()) { | |
| 178 | .value => |val| { | |
| 179 | s.decl.raw_value = val; | |
| 180 | s.decl.analysis = .complete; | |
| 181 | }, | |
| 182 | .index => @panic("TODO"), | |
| 183 | } | |
| 184 | }, | |
| 185 | } | |
| 186 | ||
| 187 | return s.decl.value(); | |
| 188 | } | |
| 189 | ||
| 190 | /// TODO: Delete this legacy code. This will be replaced by analyzeDeclaration and a lambda | |
| 191 | /// function (See #45) | |
| 192 | pub fn analyzeFunctionDeclaration(s: *Sema) Error!Value { | |
| 135 | 193 | const t = tracer.trace(@src()); |
| 136 | 194 | defer t.end(); |
| 137 | 195 | |
| ... | ... | @@ -164,7 +222,7 @@ pub fn analyzeDeclaration(s: *Sema) Error!Value { |
| 164 | 222 | ); |
| 165 | 223 | |
| 166 | 224 | const func_prototype = (try s.ip.get(s.gpa, .{ |
| 167 | .type_function = .{ | |
| 225 | .function_type = .{ | |
| 168 | 226 | .param_types = s.params, |
| 169 | 227 | .return_type = return_type, |
| 170 | 228 | }, |
| ... | ... | @@ -173,14 +231,14 @@ pub fn analyzeDeclaration(s: *Sema) Error!Value { |
| 173 | 231 | .func = .{ |
| 174 | 232 | .ty = func_prototype, |
| 175 | 233 | .owner_decl = s.decl_index, |
| 176 | .teg_body_inst = s.decl.teg_decl_index.unwrap().?, | |
| 234 | .teg_body_inst = s.decl.teg_decl_index, | |
| 177 | 235 | }, |
| 178 | 236 | }); |
| 179 | s.decl.val = value; | |
| 180 | 237 | |
| 181 | s.decl.analysis = .in_progress_prototype_available; | |
| 238 | s.decl.raw_value = value.typeOf(s.ip).value(); | |
| 239 | s.decl.analysis = .in_progress_type_available; | |
| 182 | 240 | |
| 183 | const root_block = fn_decl.body; | |
| 241 | const root_block = decl.body; | |
| 184 | 242 | switch (root_block.decode()) { |
| 185 | 243 | .value => |val| { |
| 186 | 244 | const coerced = try s.coerceValue( |
| ... | ... | @@ -200,6 +258,9 @@ pub fn analyzeDeclaration(s: *Sema) Error!Value { |
| 200 | 258 | }, |
| 201 | 259 | } |
| 202 | 260 | |
| 261 | s.decl.analysis = .complete; | |
| 262 | s.decl.raw_value = value; | |
| 263 | ||
| 203 | 264 | return value; |
| 204 | 265 | } |
| 205 | 266 | |
| ... | ... | @@ -268,31 +329,73 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 268 | 329 | return s.makeValueAir(.{ .string = string.slice(s.code.string_bytes) }); |
| 269 | 330 | }, |
| 270 | 331 | .number => { |
| 271 | const number: dod.Float = s.code.instPayload(.number, in); | |
| 332 | const number: store.Float = s.code.instPayload(.number, in); | |
| 272 | 333 | return s.makeValueAir(.{ .comptime_float = number.decode() }); |
| 273 | 334 | }, |
| 274 | 335 | .js => { |
| 275 | 336 | const operand_ref = s.code.instData(.js, in).operand; |
| 276 | const operand = try s.resolveRefComptimeType(in.op(.operand), operand_ref, .string); | |
| 337 | const operand = try s.resolveCoerceValue(in.op(.operand), operand_ref, .string); | |
| 277 | 338 | return s.addInstRef(.js_global_ref, operand.asString(s.ip)); |
| 278 | 339 | }, |
| 279 | 340 | .field => { |
| 280 | 341 | const field: Teg.Inst.Bin = s.code.instPayload(.field, in); |
| 281 | const base = try s.resolveRefRuntime(field.lhs); | |
| 342 | const base = try s.resolveRef(field.lhs); | |
| 282 | 343 | |
| 283 | const rhs = try s.resolveRefComptimeType(in.op(.rhs), field.rhs, .string); | |
| 344 | const rhs = try s.resolveCoerceValue(in.op(.rhs), field.rhs, .string); | |
| 284 | 345 | const string = rhs.asString(s.ip); |
| 285 | 346 | |
| 286 | const base_type = s.typeOfRef(base); | |
| 287 | switch (base_type) { | |
| 288 | .@"opaque" => { | |
| 289 | return s.addInstRef(.field, .{ | |
| 290 | .base = base, | |
| 291 | .field = try string.tryToNullTerminatedString(s.ip, s.gpa), | |
| 347 | const ty = s.typeOfRef(base); | |
| 348 | ||
| 349 | if (ty == .@"opaque") { | |
| 350 | return s.addInstRef(.field_opaque, .{ | |
| 351 | .base = base, | |
| 352 | .field = string.tryToNullTerminatedString(s.ip, s.gpa) catch |e| switch (e) { | |
| 353 | error.HasNulls => @panic("TODO: handle null"), | |
| 354 | else => return e, | |
| 355 | }, | |
| 356 | }); | |
| 357 | } | |
| 358 | ||
| 359 | if (ty.toStructType(s.ip)) |struct_type| { | |
| 360 | const found_index: u32 = found_index: { | |
| 361 | const lookup = string.tryToNullTerminatedString( | |
| 362 | s.ip, | |
| 363 | s.gpa, | |
| 364 | ) catch |e| switch (e) { | |
| 365 | error.HasNulls => break :found_index null, | |
| 366 | else => return e, | |
| 367 | }; | |
| 368 | ||
| 369 | break :found_index for (struct_type.field_keys, 0..) |key, i| { | |
| 370 | if (key == lookup) | |
| 371 | break @as(u32, @intCast(i)); | |
| 372 | } else null; | |
| 373 | } orelse { | |
| 374 | return s.fail(.field_lookup_failed, .{ | |
| 375 | .ty = ty, | |
| 376 | .src = in.op(.rhs).lazyNode(s.decl_index), | |
| 377 | .field = rhs, | |
| 292 | 378 | }); |
| 293 | }, | |
| 294 | else => |tag| std.debug.panic("TODO: field analyze with base .{s}", .{@tagName(tag)}), | |
| 379 | }; | |
| 380 | ||
| 381 | switch (base.decode()) { | |
| 382 | .value => |value| { | |
| 383 | _, const trail = value.valueData(s.ip, .struct_value).getTrail(s.ip); | |
| 384 | return trail.at(&s.ip.extra, Value, found_index).toAir(); | |
| 385 | }, | |
| 386 | .index => |index| { | |
| 387 | return s.addInstRef(.field_struct, .{ | |
| 388 | .base = index, | |
| 389 | .field_index = found_index, | |
| 390 | }); | |
| 391 | }, | |
| 392 | } | |
| 295 | 393 | } |
| 394 | ||
| 395 | return s.fail(.field_invalid_type, .{ | |
| 396 | .ty = ty, | |
| 397 | .src = in.op(.rhs).lazyNode(s.decl_index), | |
| 398 | }); | |
| 296 | 399 | }, |
| 297 | 400 | .load_decl => { |
| 298 | 401 | const request: Teg.Inst.StrNode = s.code.instData(.load_decl, in); |
| ... | ... | @@ -326,8 +429,8 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 326 | 429 | const bin: Teg.Inst.Bin = s.code.instPayload(tag, in); |
| 327 | 430 | const op = @field(BinaryOp, @tagName(tag)); |
| 328 | 431 | |
| 329 | const lhs = try s.resolveRefRuntime(bin.lhs); | |
| 330 | const rhs = try s.resolveRefRuntime(bin.rhs); | |
| 432 | const lhs = try s.resolveRef(bin.lhs); | |
| 433 | const rhs = try s.resolveRef(bin.rhs); | |
| 331 | 434 | |
| 332 | 435 | const left_ty = s.typeOfRef(lhs); |
| 333 | 436 | const right_ty = s.typeOfRef(rhs); |
| ... | ... | @@ -350,23 +453,47 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 350 | 453 | .rhs = right, |
| 351 | 454 | }); |
| 352 | 455 | }, |
| 353 | inline .negate, .bool_not => |tag| { | |
| 354 | const un: Teg.Inst.RefNode = s.code.instData(tag, in); | |
| 456 | .bool_not => { | |
| 457 | const un: Teg.Inst.RefNode = s.code.instData(.bool_not, in); | |
| 355 | 458 | |
| 356 | const operand = try s.resolveRefRuntime(un.operand); | |
| 459 | const operand = try s.resolveCoerceRef(in.op(.operand), un.operand, .bool); | |
| 357 | 460 | |
| 358 | const operand_type = s.typeOfRef(operand); | |
| 359 | _ = operand_type; // autofix | |
| 461 | // Constant propagation | |
| 462 | if (operand.value()) |value| { | |
| 463 | assert(value == .bool_false or value == .bool_true); | |
| 464 | return if (value == .bool_true) | |
| 465 | Value.bool_false.toAir() | |
| 466 | else | |
| 467 | Value.bool_true.toAir(); | |
| 468 | } | |
| 360 | 469 | |
| 361 | // TODO: validate type for operation | |
| 470 | return s.addInstRef(.bool_not, .{ .operand = operand }); | |
| 471 | }, | |
| 472 | .negate => { | |
| 473 | const un: Teg.Inst.RefNode = s.code.instData(.negate, in); | |
| 362 | 474 | |
| 363 | return s.addInstRef(tag.airTag(), .{ | |
| 364 | .operand = operand, | |
| 365 | }); | |
| 475 | const operand = try s.resolveRef(un.operand); | |
| 476 | ||
| 477 | const ty = s.typeOfRef(operand); | |
| 478 | switch (ty) { | |
| 479 | .numeric_literal, .int32, .float => {}, | |
| 480 | else => return s.fail(.un_op_mismatch, .{ | |
| 481 | .src = in.op(.operand).lazyNode(s.decl_index), | |
| 482 | .ty = ty, | |
| 483 | }), | |
| 484 | } | |
| 485 | ||
| 486 | if (operand.value()) |value| { | |
| 487 | return s.makeValueAir(.{ | |
| 488 | .comptime_float = -value.toFloat(s.ip).?, | |
| 489 | }); | |
| 490 | } | |
| 491 | ||
| 492 | return s.addInstRef(.negate, .{ .operand = operand }); | |
| 366 | 493 | }, |
| 367 | 494 | .@"break" => { |
| 368 | 495 | const brk: Teg.Inst.Break = s.code.instPayload(.@"break", in); |
| 369 | const operand = try s.resolveRefRuntime(brk.operand); | |
| 496 | const operand = try s.resolveRef(brk.operand); | |
| 370 | 497 | const block_ref = s.inst_map.get(brk.block).?.decode().index; // block ref is resolved by teg |
| 371 | 498 | |
| 372 | 499 | const coerced = if (s.temp_result_map.get(brk.block)) |ty| |
| ... | ... | @@ -381,7 +508,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 381 | 508 | }, |
| 382 | 509 | .call => { |
| 383 | 510 | const call: Teg.Inst.Call = s.code.instPayload(.call, in); |
| 384 | const base = try s.resolveRefRuntime(call.base); | |
| 511 | const base = try s.resolveRef(call.base); | |
| 385 | 512 | const base_type = s.typeOfRef(base); |
| 386 | 513 | |
| 387 | 514 | const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len); |
| ... | ... | @@ -405,7 +532,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 405 | 532 | |
| 406 | 533 | // validate function args |
| 407 | 534 | for (proto.param_types, call.args.slice(&s.code), 0..) |param, arg, i| { |
| 408 | const ref = try s.resolveRefRuntime(arg); | |
| 535 | const ref = try s.resolveRef(arg); | |
| 409 | 536 | const coerced = try s.coerceRef( |
| 410 | 537 | in.op(Report.Operand.argument(@intCast(i))), |
| 411 | 538 | ref, |
| ... | ... | @@ -417,7 +544,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 417 | 544 | for (call.args.slice(&s.code), 0..) |arg, i| { |
| 418 | 545 | const ref = try s.ensureValueRuntime( |
| 419 | 546 | in.op(Report.Operand.argument(@intCast(i))), |
| 420 | try s.resolveRefRuntime(arg), | |
| 547 | try s.resolveRef(arg), | |
| 421 | 548 | ); |
| 422 | 549 | scratch[i] = ref; |
| 423 | 550 | } |
| ... | ... | @@ -438,14 +565,14 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 438 | 565 | }, |
| 439 | 566 | .js_new => { |
| 440 | 567 | const call: Teg.Inst.JsNew = s.code.instPayload(.js_new, in); |
| 441 | const base = try s.resolveRefRuntime(call.base); | |
| 568 | const base = try s.resolveRef(call.base); | |
| 442 | 569 | |
| 443 | 570 | const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len); |
| 444 | 571 | defer s.arena.free(scratch); |
| 445 | 572 | for (call.args.slice(&s.code), 0..) |arg, i| { |
| 446 | 573 | const ref = try s.ensureValueRuntime( |
| 447 | 574 | in.op(Report.Operand.argument(@intCast(i))), |
| 448 | try s.resolveRefRuntime(arg), | |
| 575 | try s.resolveRef(arg), | |
| 449 | 576 | ); |
| 450 | 577 | scratch[i] = ref; |
| 451 | 578 | } |
| ... | ... | @@ -462,7 +589,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 462 | 589 | }); |
| 463 | 590 | }, |
| 464 | 591 | .ensure_void => { |
| 465 | const operand = try s.resolveRefRuntimeExtra(s.code.instData(.ensure_void, in).operand, false); | |
| 592 | const operand = try s.resolveRefExtra(s.code.instData(.ensure_void, in).operand, false); | |
| 466 | 593 | const t = s.typeOfRef(operand); |
| 467 | 594 | _ = t; |
| 468 | 595 | // if (!t.isEnsureVoid(s.ip)) { |
| ... | ... | @@ -473,7 +600,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 473 | 600 | return InternPool.Value.void.toAir(); |
| 474 | 601 | }, |
| 475 | 602 | .discard => { |
| 476 | const operand = try s.resolveRefRuntimeExtra(s.code.instData(.discard, in).operand, false); | |
| 603 | const operand = try s.resolveRefExtra(s.code.instData(.discard, in).operand, false); | |
| 477 | 604 | const t = s.typeOfRef(operand); |
| 478 | 605 | if (t == .void) { |
| 479 | 606 | std.debug.print("oh no, we have a useless _ = void;\n", .{}); |
| ... | ... | @@ -483,7 +610,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 483 | 610 | .@"if" => { |
| 484 | 611 | const data: Teg.Inst.If = s.code.instData(.@"if", in).extra_index.get(&s.code); |
| 485 | 612 | |
| 486 | const cond_raw = try s.resolveRefRuntime(data.cond); | |
| 613 | const cond_raw = try s.resolveRef(data.cond); | |
| 487 | 614 | const cond = try s.coerceRef(in.op(.operand), cond_raw, .bool); |
| 488 | 615 | |
| 489 | 616 | // If known at comptime, only analyze the taken path |
| ... | ... | @@ -517,7 +644,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 517 | 644 | .memory => { |
| 518 | 645 | const data = s.code.instData(.memory, in); |
| 519 | 646 | |
| 520 | const initial_value = try s.resolveRefRuntime(data.operand); | |
| 647 | const initial_value = try s.resolveRef(data.operand); | |
| 521 | 648 | |
| 522 | 649 | const runtime_known = try s.ensureValueRuntime( |
| 523 | 650 | in.op(.operand), |
| ... | ... | @@ -535,7 +662,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 535 | 662 | |
| 536 | 663 | const location = s.mapTeg(data.memory.toRef()).decode().index; |
| 537 | 664 | s.markInst(location); |
| 538 | const operand = try s.resolveRefRuntime(data.operand); | |
| 665 | const operand = try s.resolveRef(data.operand); | |
| 539 | 666 | const operand_coerced = try s.coerceRef( |
| 540 | 667 | in.op(.rhs), |
| 541 | 668 | operand, |
| ... | ... | @@ -548,23 +675,23 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 548 | 675 | }, |
| 549 | 676 | .as => { |
| 550 | 677 | const as: Teg.Inst.As = s.code.instPayload(.as, in); |
| 551 | const coerce_to = try s.resolveRefComptimeType(in.op(.lhs), as.type, .type); | |
| 552 | const operand = try s.resolveRefRuntime(as.operand); | |
| 678 | const coerce_to = try s.resolveCoerceValue(in.op(.lhs), as.type, .type); | |
| 679 | const operand = try s.resolveRef(as.operand); | |
| 553 | 680 | return try s.coerceRef(in.op(.rhs), operand, coerce_to.toType(s.ip).?); |
| 554 | 681 | }, |
| 555 | 682 | .@"comptime" => { |
| 556 | 683 | const node: Teg.Inst.RefNode = s.code.instData(.@"comptime", in); |
| 557 | const value = try s.resolveRefComptimeAny(node.operand); | |
| 684 | const value = try s.resolveValue(node.operand); | |
| 558 | 685 | return value.toAir(); |
| 559 | 686 | }, |
| 560 | 687 | .type_of => { |
| 561 | 688 | const node: Teg.Inst.RefNode = s.code.instData(.type_of, in); |
| 562 | const value = try s.resolveRefComptimeAny(node.operand); | |
| 689 | const value = try s.resolveValue(node.operand); | |
| 563 | 690 | return value.typeOf(s.ip).value().toAir(); |
| 564 | 691 | }, |
| 565 | 692 | .compile_error => { |
| 566 | 693 | const node: Teg.Inst.RefNode = s.code.instData(.compile_error, in); |
| 567 | const value = try s.resolveRefComptimeType( | |
| 694 | const value = try s.resolveCoerceValue( | |
| 568 | 695 | in.op(Report.Operand.argument(0)), |
| 569 | 696 | node.operand, |
| 570 | 697 | .string, |
| ... | ... | @@ -575,6 +702,162 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 575 | 702 | .msg = try s.report.addString(str), |
| 576 | 703 | }); |
| 577 | 704 | }, |
| 705 | .struct_decl => { | |
| 706 | const node: Teg.Inst.StructDecl = s.code.instPayload(.struct_decl, in); | |
| 707 | ||
| 708 | const scratch_top = s.scratch.items.len; | |
| 709 | try s.scratch.ensureUnusedCapacity(s.arena, node.field_len * 2); | |
| 710 | s.scratch.items.len += node.field_len * 2; | |
| 711 | defer s.scratch.shrinkRetainingCapacity(scratch_top); | |
| 712 | ||
| 713 | for ( | |
| 714 | node.keys(&s.code), | |
| 715 | // Keeping the slice pointer is fine for this loop, since scratch is not resized | |
| 716 | s.scratch.items[scratch_top..][0..node.field_len], | |
| 717 | ) |key, *dest| { | |
| 718 | dest.* = @intFromEnum(try s.ip.getOrPutString( | |
| 719 | s.gpa, | |
| 720 | key.slice(s.code.string_bytes), | |
| 721 | .no_embedded_nulls, | |
| 722 | )); | |
| 723 | } | |
| 724 | ||
| 725 | for ( | |
| 726 | node.types(&s.code), | |
| 727 | // We cannot reuse the pointer from scratch between calls to sema | |
| 728 | scratch_top + node.field_len.., | |
| 729 | 0.., | |
| 730 | ) |ty, i, arg_i| { | |
| 731 | s.scratch.items[i] = @intFromEnum(try s.resolveCoerceValue( | |
| 732 | in.op(Report.Operand.argument(@intCast(arg_i))), | |
| 733 | ty, | |
| 734 | .type, | |
| 735 | )); | |
| 736 | } | |
| 737 | ||
| 738 | const keys = s.scratch.items[scratch_top..][0..node.field_len]; | |
| 739 | const types = s.scratch.items[scratch_top + node.field_len ..][0..node.field_len]; | |
| 740 | ||
| 741 | return try s.makeValueAir(.{ .struct_type = .{ | |
| 742 | .field_keys = @ptrCast(keys), | |
| 743 | .field_types = @ptrCast(types), | |
| 744 | } }); | |
| 745 | }, | |
| 746 | .struct_init => { | |
| 747 | const node: Teg.Inst.StructInit = s.code.instPayload(.struct_init, in); | |
| 748 | ||
| 749 | // Ensure we have a valid struct to operate on | |
| 750 | const ty = try s.resolveType(in.op(.operand), node.base_type); | |
| 751 | const struct_type = ty.toStructType(s.ip) orelse return s.fail( | |
| 752 | .struct_init_invalid_type, | |
| 753 | .{ | |
| 754 | .src = in.op(.operand).lazyNode(s.decl_index), | |
| 755 | .ty = ty, | |
| 756 | }, | |
| 757 | ); | |
| 758 | ||
| 759 | var has_errors = false; | |
| 760 | var is_constant = true; | |
| 761 | ||
| 762 | // Allocate scratch space to store the values as they are being prepared. | |
| 763 | // TODO: make this an abstraction | |
| 764 | const scratch_top = s.scratch.items.len; | |
| 765 | ||
| 766 | try s.scratch.ensureUnusedCapacity(s.arena, struct_type.field_types.len); | |
| 767 | const storage: []Air.Inst.Ref.Optional = | |
| 768 | @ptrCast(s.scratch.unusedCapacitySlice()[0..struct_type.field_types.len]); | |
| 769 | s.scratch.items.len += storage.len; | |
| 770 | @memset(storage, .none); | |
| 771 | ||
| 772 | const scratch_end = s.scratch.items.len; | |
| 773 | defer { | |
| 774 | const from = s.scratch.items[scratch_end..]; | |
| 775 | const dest = s.scratch.items[scratch_top..][0..from.len]; | |
| 776 | std.mem.copyForwards(u32, dest, from); | |
| 777 | s.scratch.shrinkRetainingCapacity(scratch_top + from.len); | |
| 778 | } | |
| 779 | ||
| 780 | for (node.keys(&s.code), node.inits(&s.code), 0..) |init_key, init_value, i| { | |
| 781 | // The order of `storage` is as ordered by the type, but this | |
| 782 | // can be in a different ordering than the initialization. We | |
| 783 | // guarantee the ordering the initializers are visited is in the | |
| 784 | // order they are declared. | |
| 785 | const found_index = index: for (struct_type.field_keys, 0..) |decl_key, j| { | |
| 786 | if (decl_key.eqlSlice(init_key.slice(s.code.string_bytes), s.ip)) | |
| 787 | break :index j; | |
| 788 | } else { | |
| 789 | has_errors = true; | |
| 790 | try s.addError(.struct_init_unknown_field, .{ | |
| 791 | .ty = ty, | |
| 792 | .src = in.op(Report.Operand.argument(@intCast(i))).lazyNode(s.decl_index), | |
| 793 | }); | |
| 794 | continue; | |
| 795 | }; | |
| 796 | if (storage[found_index] != .none) { | |
| 797 | has_errors = true; | |
| 798 | try s.addError(.struct_init_duplicate_field, .{ | |
| 799 | .ty = ty, | |
| 800 | .src = in.op(Report.Operand.argument(@intCast(i))).lazyNode(s.decl_index), | |
| 801 | }); | |
| 802 | continue; | |
| 803 | } | |
| 804 | // TODO: calling coerceRef / resolveRefRuntime will clobber the `storage` pointer | |
| 805 | const coerced = s.resolveCoerceRef( | |
| 806 | in.op(Report.Operand.argument(@intCast(i))), | |
| 807 | init_value, | |
| 808 | struct_type.field_types[found_index], | |
| 809 | ) catch |e| switch (e) { | |
| 810 | error.AnalysisFail => { | |
| 811 | // Instead of bailing out with `try`, we will continue the loop. | |
| 812 | has_errors = true; | |
| 813 | // Mark something non-null so that duplicate field errors are emitted. | |
| 814 | storage[found_index] = comptime Value.void.toAir().toOptional(); | |
| 815 | continue; | |
| 816 | }, | |
| 817 | else => return e, | |
| 818 | }; | |
| 819 | if (coerced.kind == .index) is_constant = false; | |
| 820 | storage[found_index] = coerced.toOptional(); | |
| 821 | } | |
| 822 | ||
| 823 | // Check for missing fields | |
| 824 | { | |
| 825 | var i: usize = 0; | |
| 826 | while (mem.indexOfScalarPos(Air.Inst.Ref.Optional, storage, i, .none)) |index| { | |
| 827 | try s.addError(.struct_init_missing_field, .{ | |
| 828 | .ty = ty, | |
| 829 | .field = @intCast(index), | |
| 830 | .src = in.op(.main_node).lazyNode(s.decl_index), | |
| 831 | }); | |
| 832 | i = index + 1; | |
| 833 | } | |
| 834 | if (i != 0) has_errors = true; | |
| 835 | } | |
| 836 | ||
| 837 | if (has_errors) | |
| 838 | return Error.AnalysisFail; | |
| 839 | ||
| 840 | if (is_constant) { | |
| 841 | // Convert all items in storage into a Value type | |
| 842 | const result_storage: []Value = @ptrCast(storage); | |
| 843 | for (storage, result_storage) |ref, *value| { | |
| 844 | value.* = ref.unwrap().?.decode().value; | |
| 845 | } | |
| 846 | return try s.makeValueAir(.{ .struct_value = .{ | |
| 847 | .ty = ty, | |
| 848 | .field_values = result_storage, | |
| 849 | } }); | |
| 850 | } else { | |
| 851 | // TODO: make this less error prone | |
| 852 | const result_storage: []u32 = @ptrCast(storage); | |
| 853 | const value_index = try s.air_extra.alloc(s.gpa, result_storage.len); | |
| 854 | @memcpy(s.air_extra.list.items[value_index..].ptr, result_storage); | |
| 855 | return try s.addInstRef(.struct_init, .{ | |
| 856 | .ty = ty, | |
| 857 | .field_values = value_index, | |
| 858 | }); | |
| 859 | } | |
| 860 | }, | |
| 578 | 861 | else => |tag| std.debug.panic("TODO: analyze of .{s}", .{@tagName(tag)}), |
| 579 | 862 | } |
| 580 | 863 | } |
| ... | ... | @@ -591,7 +874,7 @@ fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref { |
| 591 | 874 | const scratch_top = s.scratch.items.len; |
| 592 | 875 | defer s.scratch.shrinkRetainingCapacity(scratch_top); |
| 593 | 876 | |
| 594 | const visited = s.resolveRefRuntime(in) catch |err| switch (err) { | |
| 877 | const visited = s.resolveRef(in) catch |err| switch (err) { | |
| 595 | 878 | error.HitNoReturn => Value.void.toAir(), |
| 596 | 879 | else => return err, |
| 597 | 880 | }; |
| ... | ... | @@ -614,7 +897,7 @@ fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref { |
| 614 | 897 | return (try block.finish(s, scratch_top)).toRef(); |
| 615 | 898 | } |
| 616 | 899 | |
| 617 | fn resolveRefRuntimeExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) Error!Air.Inst.Ref { | |
| 900 | fn resolveRefExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) Error!Air.Inst.Ref { | |
| 618 | 901 | const index = ref.toIndex() orelse { |
| 619 | 902 | return ref.toValue().?.toAir(); |
| 620 | 903 | }; |
| ... | ... | @@ -632,11 +915,11 @@ fn resolveRefRuntimeExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) Error!Air.Ins |
| 632 | 915 | return s.analyzeCode(index); |
| 633 | 916 | } |
| 634 | 917 | |
| 635 | fn resolveRefRuntime(s: *Sema, ref: Teg.Inst.Ref) Error!Air.Inst.Ref { | |
| 636 | return resolveRefRuntimeExtra(s, ref, true); | |
| 918 | fn resolveRef(s: *Sema, ref: Teg.Inst.Ref) Error!Air.Inst.Ref { | |
| 919 | return resolveRefExtra(s, ref, true); | |
| 637 | 920 | } |
| 638 | 921 | |
| 639 | fn resolveRefComptimeAny(s: *Sema, ref: Teg.Inst.Ref) !Value { | |
| 922 | fn resolveValue(s: *Sema, ref: Teg.Inst.Ref) !Value { | |
| 640 | 923 | const index = switch (ref.decode()) { |
| 641 | 924 | .value => |value| return value, |
| 642 | 925 | .index => |index| index, |
| ... | ... | @@ -655,21 +938,69 @@ fn resolveRefComptimeAny(s: *Sema, ref: Teg.Inst.Ref) !Value { |
| 655 | 938 | return s.makeValue(.{ .string = string.slice(s.code.string_bytes) }); |
| 656 | 939 | }, |
| 657 | 940 | |
| 658 | else => |tag| std.debug.panic("TODO: comptime analyze of .{s}", .{@tagName(tag)}), | |
| 941 | .block => { | |
| 942 | // Currently only one pattern is supported: | |
| 943 | // | |
| 944 | // %2 = block [ | |
| 945 | // %1 = load_decl X | |
| 946 | // %3 = break(%3, %2) | |
| 947 | // ] | |
| 948 | // | |
| 949 | // This is to support function arguments with structs. | |
| 950 | // The rest of this will be implemented in https://github.com/paperdave/wl/issues/44 | |
| 951 | special_cased: { | |
| 952 | const index_list: Teg.IndexList = s.code.instPayload(.block, index); | |
| 953 | if (index_list.len != 2) break :special_cased; | |
| 954 | const indexes = index_list.slice(&s.code); | |
| 955 | const load_inst = indexes[0]; | |
| 956 | const break_inst = indexes[1]; | |
| 957 | ||
| 958 | if (s.code.instTag(break_inst) != .@"break") break :special_cased; | |
| 959 | const break_data: Teg.Inst.Break = s.code.instPayload(.@"break", break_inst); | |
| 960 | if (break_data.operand != load_inst.toRef()) break :special_cased; | |
| 961 | if (break_data.block != index) break :special_cased; | |
| 962 | ||
| 963 | if (s.code.instTag(load_inst) != .load_decl) break :special_cased; | |
| 964 | ||
| 965 | const request: Teg.Inst.StrNode = s.code.instData(.load_decl, load_inst); | |
| 966 | const decl_name = try s.ip.getOrPutString( | |
| 967 | s.gpa, | |
| 968 | request.string.slice(s.code.string_bytes), | |
| 969 | .no_embedded_nulls, | |
| 970 | ); | |
| 971 | const decl_index = s.lookupDecl(decl_name) orelse { | |
| 972 | std.debug.print("did not find {s}\n", .{decl_name.toSlice(s.ip)}); | |
| 973 | return error.AnalysisFail; | |
| 974 | }; | |
| 975 | return try s.resolveDecl(load_inst.op(.main_node), decl_index); | |
| 976 | } | |
| 977 | ||
| 978 | std.debug.panic("TODO: advanced comptime interpreter", .{}); | |
| 979 | }, | |
| 980 | ||
| 981 | else => |tag| std.debug.panic("TODO: comptime analyze of .{s} {d}", .{ @tagName(tag), @intFromEnum(index) }), | |
| 659 | 982 | } |
| 660 | 983 | } |
| 661 | 984 | |
| 662 | fn resolveRefComptimeType(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref, ty: Type) !Value { | |
| 663 | const any = try s.resolveRefComptimeAny(ref); | |
| 985 | fn resolveCoerceValue(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref, ty: Type) !Value { | |
| 986 | const any = try s.resolveValue(ref); | |
| 664 | 987 | return try s.coerceValue(src, any, ty); |
| 665 | 988 | } |
| 666 | 989 | |
| 990 | fn resolveCoerceRef(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref, ty: Type) !Air.Inst.Ref { | |
| 991 | const any = try s.resolveRef(ref); | |
| 992 | return try s.coerceRef(src, any, ty); | |
| 993 | } | |
| 994 | ||
| 667 | 995 | fn resolveType(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref) !Type { |
| 668 | return (try s.resolveRefComptimeType(src, ref, .type)).toTypeUnsafe(s.ip); | |
| 996 | return (try s.resolveCoerceValue(src, ref, .type)).toTypeUnsafe(s.ip); | |
| 669 | 997 | } |
| 670 | 998 | |
| 671 | /// Coerce `value` runtime ref to `coerce_type`, and emit an error at `teg_ref`'s location | |
| 999 | /// Coerce `value` runtime ref to `coerce_type`, and emit an error at `src`'s location | |
| 672 | 1000 | fn coerceRef(s: *Sema, src: InstOp, value: Air.Inst.Ref, to: Type) !Air.Inst.Ref { |
| 1001 | const t = tracer.traceFmt(@src(), "{} to {}", .{ value.dump(s.ip), to.dump(s.ip) }); | |
| 1002 | defer t.end(); | |
| 1003 | ||
| 673 | 1004 | const from = s.typeOfRef(value); |
| 674 | 1005 | if (from == to) { |
| 675 | 1006 | return value; |
| ... | ... | @@ -721,6 +1052,7 @@ fn coerceRef(s: *Sema, src: InstOp, value: Air.Inst.Ref, to: Type) !Air.Inst.Ref |
| 721 | 1052 | return s.coercionError(src, from, to); |
| 722 | 1053 | } |
| 723 | 1054 | |
| 1055 | /// Same as `coerceRef` but on a compile-time known value | |
| 724 | 1056 | fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value { |
| 725 | 1057 | const from = value.typeOf(s.ip); |
| 726 | 1058 | if (from == to) { |
| ... | ... | @@ -728,11 +1060,10 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value { |
| 728 | 1060 | } |
| 729 | 1061 | switch (to) { |
| 730 | 1062 | .@"opaque" => { |
| 731 | ||
| 732 | // | |
| 1063 | // TODO: | |
| 733 | 1064 | }, |
| 734 | 1065 | .bool => { |
| 735 | // | |
| 1066 | // TODO: | |
| 736 | 1067 | }, |
| 737 | 1068 | .int32 => switch (from) { |
| 738 | 1069 | .int32 => unreachable, // same type handled |
| ... | ... | @@ -818,8 +1149,10 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) !Air.Inst.Ref { |
| 818 | 1149 | => {}, |
| 819 | 1150 | _ => switch (ty.tag(s.ip)) { |
| 820 | 1151 | .simple_type => unreachable, // handled above |
| 1152 | ||
| 821 | 1153 | // Allowed at runtime |
| 822 | .type_function => {}, | |
| 1154 | .function_type => {}, | |
| 1155 | .struct_type => {}, | |
| 823 | 1156 | }, |
| 824 | 1157 | } |
| 825 | 1158 | }, |
| ... | ... | @@ -867,7 +1200,7 @@ fn resolveDecl(s: *Sema, src: InstOp, decl_index: Decl.Index) Error!Value { |
| 867 | 1200 | }); |
| 868 | 1201 | }, |
| 869 | 1202 | else => { |
| 870 | return decl.val.?; | |
| 1203 | return decl.value(); | |
| 871 | 1204 | }, |
| 872 | 1205 | } |
| 873 | 1206 | } |
| ... | ... | @@ -904,8 +1237,12 @@ const BinaryOp = enum { |
| 904 | 1237 | }; |
| 905 | 1238 | |
| 906 | 1239 | fn binTypeResolution(s: *Sema, src: Teg.Inst.Index, op: BinaryOp, lhs: Type, rhs: Type) !Type { |
| 907 | if (lhs == .@"opaque") return rhs; | |
| 908 | if (rhs == .@"opaque") return rhs; | |
| 1240 | if (lhs == .@"opaque") { | |
| 1241 | return if (rhs == .numeric_literal) .float else rhs; | |
| 1242 | } | |
| 1243 | if (rhs == .@"opaque") { | |
| 1244 | return if (lhs == .numeric_literal) .float else lhs; | |
| 1245 | } | |
| 909 | 1246 | |
| 910 | 1247 | switch (lhs) { |
| 911 | 1248 | .string => if (op == .add) switch (rhs) { |
| ... | ... | @@ -1016,9 +1353,7 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value { |
| 1016 | 1353 | }, |
| 1017 | 1354 | else => unreachable, |
| 1018 | 1355 | }, |
| 1019 | inline .lt, | |
| 1020 | .gt, | |
| 1021 | => |tag| { | |
| 1356 | inline .lt, .gt => |tag| { | |
| 1022 | 1357 | const l = lhs.toFloat(s.ip) orelse |
| 1023 | 1358 | unreachable; // must be coerced |
| 1024 | 1359 | const r = rhs.toFloat(s.ip) orelse |
| ... | ... | @@ -1034,8 +1369,7 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value { |
| 1034 | 1369 | if (result) .bool_true else .bool_false, |
| 1035 | 1370 | ); |
| 1036 | 1371 | }, |
| 1037 | .eq, | |
| 1038 | => { | |
| 1372 | .eq => { | |
| 1039 | 1373 | const result = lhs == rhs; |
| 1040 | 1374 | return @as( |
| 1041 | 1375 | InternPool.Value, |
| ... | ... | @@ -1045,7 +1379,6 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value { |
| 1045 | 1379 | } |
| 1046 | 1380 | } |
| 1047 | 1381 | |
| 1048 | /// --- helper --- | |
| 1049 | 1382 | const IncompleteBlock = struct { |
| 1050 | 1383 | block_index: Air.Inst.Index, |
| 1051 | 1384 | |
| ... | ... | @@ -1177,30 +1510,26 @@ const math = std.math; |
| 1177 | 1510 | const Allocator = std.mem.Allocator; |
| 1178 | 1511 | const assert = std.debug.assert; |
| 1179 | 1512 | const log = std.log.scoped(.sema); |
| 1513 | const DynamicBitSetUnmanaged = std.DynamicBitSetUnmanaged; | |
| 1180 | 1514 | |
| 1181 | inline fn assertWithMessage(cond: bool) bool { | |
| 1182 | if (std.debug.runtime_safety) { | |
| 1183 | return !cond; | |
| 1184 | } else if (!cond) { | |
| 1185 | unreachable; // assertion failure | |
| 1186 | } | |
| 1187 | } | |
| 1515 | const core = @import("mod.zig"); | |
| 1516 | const assertFailed = core.assertFailed; | |
| 1188 | 1517 | |
| 1189 | const Comp = @import("Comp.zig"); | |
| 1518 | const Comp = core.Comp; | |
| 1190 | 1519 | const File = Comp.File; |
| 1191 | 1520 | const Decl = Comp.Decl; |
| 1192 | 1521 | const Namespace = Comp.Namespace; |
| 1193 | 1522 | |
| 1194 | const Teg = @import("Teg.zig"); | |
| 1523 | const Teg = core.Teg; | |
| 1195 | 1524 | const InstOp = Teg.InstOp; |
| 1196 | 1525 | |
| 1197 | const Air = @import("Air.zig"); | |
| 1198 | const Report = @import("Report.zig"); | |
| 1199 | const Ast = @import("Ast.zig"); | |
| 1200 | const dod = @import("array.zig"); | |
| 1526 | const Air = core.Air; | |
| 1527 | const Report = core.Report; | |
| 1528 | const Ast = core.Ast; | |
| 1529 | const store = core.array; | |
| 1201 | 1530 | const tracer = @import("tracer"); |
| 1202 | 1531 | |
| 1203 | const InternPool = @import("InternPool.zig"); | |
| 1532 | const InternPool = core.InternPool; | |
| 1204 | 1533 | const Value = InternPool.Value; |
| 1205 | 1534 | const Type = InternPool.Type; |
| 1206 | 1535 | const NullTerminatedString = InternPool.NullTerminatedString; |
src/core/Teg.Dump.zig+40-8| ... | ... | @@ -46,10 +46,10 @@ const State = struct { |
| 46 | 46 | try s.dumpInstList(writer, teg.moduleDeclarations(inst), '{', '}'); |
| 47 | 47 | }, |
| 48 | 48 | .declaration => { |
| 49 | const decl_info = teg.declData(inst); | |
| 50 | try writer.print(" ({s}) ", .{@tagName(decl_info.kind)}); | |
| 49 | const decl = teg.fullDecl(inst); | |
| 50 | try writer.print(" ({s}) ", .{@tagName(decl.data)}); | |
| 51 | 51 | |
| 52 | const name = decl_info.name.slice(teg.string_bytes); | |
| 52 | const name = decl.name.slice(teg.string_bytes); | |
| 53 | 53 | if (name.len == 0) { |
| 54 | 54 | try tty.setColor(writer, .bold); |
| 55 | 55 | try writer.writeAll("<top level code>"); |
| ... | ... | @@ -63,19 +63,19 @@ const State = struct { |
| 63 | 63 | s.indent += 1; |
| 64 | 64 | try s.newline(writer); |
| 65 | 65 | |
| 66 | switch (decl_info.kind) { | |
| 67 | .function => { | |
| 68 | const fn_data: Teg.Inst.DeclFn = teg.fullFuncDecl(inst).@"1"; | |
| 66 | switch (decl.data) { | |
| 67 | .function => |fn_data| { | |
| 69 | 68 | try writer.writeAll("params: "); |
| 70 | 69 | try s.dumpRefList(writer, fn_data.params.slice(teg), '[', ']'); |
| 71 | 70 | try s.newline(writer); |
| 72 | 71 | try writer.writeAll("return_type: "); |
| 73 | 72 | try s.dumpRef(writer, fn_data.return_type); |
| 74 | 73 | try s.newline(writer); |
| 75 | try writer.writeAll("body: "); | |
| 76 | try s.dumpRef(writer, fn_data.body); | |
| 77 | 74 | }, |
| 75 | .immutable => {}, | |
| 78 | 76 | } |
| 77 | try writer.writeAll("body: "); | |
| 78 | try s.dumpRef(writer, decl.body); | |
| 79 | 79 | s.indent -= 1; |
| 80 | 80 | try s.newline(writer); |
| 81 | 81 | try writer.writeAll("}"); |
| ... | ... | @@ -223,6 +223,38 @@ const State = struct { |
| 223 | 223 | try s.dumpRef(writer, data.operand); |
| 224 | 224 | try writer.writeAll(")"); |
| 225 | 225 | }, |
| 226 | .struct_decl => { | |
| 227 | const data: Teg.Inst.StructDecl = teg.instPayload(.struct_decl, inst); | |
| 228 | try writer.writeAll(" {"); | |
| 229 | s.indent += 1; | |
| 230 | try s.newline(writer); | |
| 231 | for (data.keys(teg), data.types(teg), 0..) |key, value, i| { | |
| 232 | if (i != 0) | |
| 233 | try s.newline(writer); | |
| 234 | try writer.print("{s}: ", .{key.slice(teg.string_bytes)}); | |
| 235 | try s.dumpRefMarker(writer, value); | |
| 236 | } | |
| 237 | s.indent -= 1; | |
| 238 | try s.newline(writer); | |
| 239 | try writer.writeAll("}"); | |
| 240 | }, | |
| 241 | .struct_init => { | |
| 242 | const data: Teg.Inst.StructInit = teg.instPayload(.struct_init, inst); | |
| 243 | try writer.writeAll("("); | |
| 244 | try s.dumpRefMarker(writer, data.base_type); | |
| 245 | try writer.writeAll(") {"); | |
| 246 | s.indent += 1; | |
| 247 | try s.newline(writer); | |
| 248 | for (data.keys(teg), data.inits(teg), 0..) |key, value, i| { | |
| 249 | if (i != 0) | |
| 250 | try s.newline(writer); | |
| 251 | try writer.print("{s}: ", .{key.slice(teg.string_bytes)}); | |
| 252 | try s.dumpRefMarker(writer, value); | |
| 253 | } | |
| 254 | s.indent -= 1; | |
| 255 | try s.newline(writer); | |
| 256 | try writer.writeAll("}"); | |
| 257 | }, | |
| 226 | 258 | } |
| 227 | 259 | } |
| 228 | 260 |
src/core/Teg.zig+94-4| ... | ... | @@ -16,7 +16,7 @@ pub const Inst = struct { |
| 16 | 16 | tag: Tag, |
| 17 | 17 | data: EncodedData, |
| 18 | 18 | |
| 19 | // todo: fully document every thing | |
| 19 | // TODO: fully document every thing | |
| 20 | 20 | pub const Tag = enum { |
| 21 | 21 | module, |
| 22 | 22 | declaration, |
| ... | ... | @@ -43,6 +43,9 @@ pub const Inst = struct { |
| 43 | 43 | |
| 44 | 44 | @"if", |
| 45 | 45 | |
| 46 | struct_decl, | |
| 47 | struct_init, | |
| 48 | ||
| 46 | 49 | add, |
| 47 | 50 | sub, |
| 48 | 51 | mul, |
| ... | ... | @@ -98,6 +101,8 @@ pub const Inst = struct { |
| 98 | 101 | .param => DataNode, |
| 99 | 102 | .store => PlNode(Store), |
| 100 | 103 | .js_new => PlNode(JsNew), |
| 104 | .struct_decl => PlNode(StructDecl), | |
| 105 | .struct_init => PlNode(StructInit), | |
| 101 | 106 | |
| 102 | 107 | // Single instruction operand |
| 103 | 108 | .memory, |
| ... | ... | @@ -271,7 +276,7 @@ pub const Inst = struct { |
| 271 | 276 | pub fn toValue(inst: Ref) ?InternPool.Value { |
| 272 | 277 | const ref_int = @intFromEnum(inst); |
| 273 | 278 | |
| 274 | // todo: is this > or >= | |
| 279 | // TODO: is this > or >= | |
| 275 | 280 | if (ref_int > Index.static_len) |
| 276 | 281 | return null; |
| 277 | 282 | |
| ... | ... | @@ -315,16 +320,57 @@ pub const Inst = struct { |
| 315 | 320 | args: RefList, |
| 316 | 321 | }; |
| 317 | 322 | |
| 323 | /// Index into extra which contains `field_len * 2` u32s. The first half | |
| 324 | /// are the keys (NullTerminatedString) and the second half are field types | |
| 325 | /// (Ref). This basically is like a specialized MultiArrayList. | |
| 326 | pub const StructDecl = struct { | |
| 327 | payload_index: u32, | |
| 328 | field_len: u32, | |
| 329 | ||
| 330 | pub fn keys(decl: StructDecl, teg: *const Teg) []const NullTerminatedString { | |
| 331 | return @ptrCast( | |
| 332 | teg.extra.list.items[decl.payload_index..][0..decl.field_len], | |
| 333 | ); | |
| 334 | } | |
| 335 | ||
| 336 | pub fn types(decl: StructDecl, teg: *const Teg) []const Ref { | |
| 337 | return @ptrCast( | |
| 338 | teg.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len], | |
| 339 | ); | |
| 340 | } | |
| 341 | }; | |
| 342 | ||
| 343 | /// This memory is laid out with the same strategy described in StructDecl | |
| 344 | pub const StructInit = struct { | |
| 345 | base_type: Ref, | |
| 346 | payload_index: u32, | |
| 347 | field_len: u32, | |
| 348 | ||
| 349 | pub fn keys(decl: StructInit, teg: *const Teg) []const NullTerminatedString { | |
| 350 | return @ptrCast( | |
| 351 | teg.extra.list.items[decl.payload_index..][0..decl.field_len], | |
| 352 | ); | |
| 353 | } | |
| 354 | ||
| 355 | pub fn inits(decl: StructInit, teg: *const Teg) []const Ref { | |
| 356 | return @ptrCast( | |
| 357 | teg.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len], | |
| 358 | ); | |
| 359 | } | |
| 360 | }; | |
| 361 | ||
| 318 | 362 | /// This will contain trailing data depending on the .kind |
| 319 | 363 | pub const Decl = struct { |
| 320 | 364 | name: NullTerminatedString, |
| 321 | 365 | hash: Cache.BinDigest, |
| 322 | 366 | kind: Kind, |
| 323 | 367 | instruction_count: u32, |
| 368 | body: Ref, | |
| 324 | 369 | |
| 325 | 370 | pub const Kind = enum { |
| 371 | // TODO: remove function, see #45 | |
| 326 | 372 | function, |
| 327 | // future: struct, let, var, etc | |
| 373 | immutable, | |
| 328 | 374 | }; |
| 329 | 375 | }; |
| 330 | 376 | |
| ... | ... | @@ -332,7 +378,6 @@ pub const Inst = struct { |
| 332 | 378 | pub const DeclFn = struct { |
| 333 | 379 | params: RefList, |
| 334 | 380 | return_type: Ref, |
| 335 | body: Ref, | |
| 336 | 381 | }; |
| 337 | 382 | }; |
| 338 | 383 | |
| ... | ... | @@ -371,6 +416,8 @@ pub const SlicedString = struct { |
| 371 | 416 | } |
| 372 | 417 | }; |
| 373 | 418 | |
| 419 | // TODO: Make the next three structs using a generic comptime thing | |
| 420 | ||
| 374 | 421 | pub const RefList = struct { |
| 375 | 422 | start: u32, |
| 376 | 423 | len: u32, |
| ... | ... | @@ -389,6 +436,15 @@ pub const IndexList = struct { |
| 389 | 436 | } |
| 390 | 437 | }; |
| 391 | 438 | |
| 439 | pub const NullTermStringList = struct { | |
| 440 | start: u32, | |
| 441 | len: u32, | |
| 442 | ||
| 443 | pub fn slice(self: IndexList, teg: *const Teg) []const NullTerminatedString { | |
| 444 | return @ptrCast(teg.extra.list.items[self.start..][0..self.len]); | |
| 445 | } | |
| 446 | }; | |
| 447 | ||
| 392 | 448 | pub fn deinit(teg: *Teg, gpa: std.mem.Allocator) void { |
| 393 | 449 | gpa.free(teg.string_bytes); |
| 394 | 450 | teg.extra.deinit(gpa); |
| ... | ... | @@ -426,10 +482,44 @@ pub fn blockInstructions(teg: *const Teg, index: Inst.Index) []const Inst.Index |
| 426 | 482 | |
| 427 | 483 | pub fn fullFuncDecl(teg: *const Teg, index: Inst.Index) struct { Inst.Decl, Inst.DeclFn } { |
| 428 | 484 | const decl, var trail = teg.instData(.declaration, index).extra_index.getTrail(teg); |
| 485 | assert(decl.kind == .function); | |
| 429 | 486 | const decl_fn = trail.read(&teg.extra, Inst.DeclFn); |
| 430 | 487 | return .{ decl, decl_fn }; |
| 431 | 488 | } |
| 432 | 489 | |
| 490 | pub const FullDecl = struct { | |
| 491 | name: NullTerminatedString, | |
| 492 | hash: Cache.BinDigest, | |
| 493 | instruction_count: u32, | |
| 494 | data: Data, | |
| 495 | body: Inst.Ref, | |
| 496 | ||
| 497 | const Data = union(enum) { | |
| 498 | function: Inst.DeclFn, | |
| 499 | immutable: struct {}, | |
| 500 | }; | |
| 501 | }; | |
| 502 | ||
| 503 | pub fn fullDecl(teg: *const Teg, index: Inst.Index) FullDecl { | |
| 504 | const decl, var trail = teg.instData(.declaration, index).extra_index.getTrail(teg); | |
| 505 | return .{ | |
| 506 | .name = decl.name, | |
| 507 | .hash = decl.hash, | |
| 508 | .instruction_count = decl.instruction_count, | |
| 509 | .body = decl.body, | |
| 510 | .data = switch (decl.kind) { | |
| 511 | inline else => |tag| @unionInit( | |
| 512 | FullDecl.Data, | |
| 513 | @tagName(tag), | |
| 514 | trail.read(&teg.extra, std.meta.FieldType( | |
| 515 | FullDecl.Data, | |
| 516 | @field(std.meta.FieldEnum(FullDecl.Data), @tagName(tag)), | |
| 517 | )), | |
| 518 | ), | |
| 519 | }, | |
| 520 | }; | |
| 521 | } | |
| 522 | ||
| 433 | 523 | pub fn declData(teg: *const Teg, index: Inst.Index) Inst.Decl { |
| 434 | 524 | return teg.instData(.declaration, index).extra_index.get(teg); |
| 435 | 525 | } |
src/core/Tokenizer.zig+2-2| ... | ... | @@ -68,11 +68,10 @@ pub const Token = struct { |
| 68 | 68 | kw_else, |
| 69 | 69 | kw_enum, |
| 70 | 70 | kw_fn, |
| 71 | kw_infer, | |
| 72 | 71 | kw_for, |
| 73 | kw_while, | |
| 74 | 72 | kw_if, |
| 75 | 73 | kw_impl, |
| 74 | kw_infer, | |
| 76 | 75 | kw_json, |
| 77 | 76 | kw_let, |
| 78 | 77 | kw_match, |
| ... | ... | @@ -83,6 +82,7 @@ pub const Token = struct { |
| 83 | 82 | kw_struct, |
| 84 | 83 | kw_trait, |
| 85 | 84 | kw_var, |
| 85 | kw_while, | |
| 86 | 86 | // sort-lines: end |
| 87 | 87 | |
| 88 | 88 | // sort-lines: start |
src/core/Transform.zig+154-35| ... | ... | @@ -1,4 +1,13 @@ |
| 1 | //! Transform pass | |
| 1 | //! Transform pass converts an Ast into a high-level/untyped instruction format. | |
| 2 | //! See Teg.zig for more details about the IR. Transforming handles: | |
| 3 | //! | |
| 4 | //! - Resolving all local variables into reference or declaration names. | |
| 5 | //! - Resolving breaks and returns to their block id. | |
| 6 | //! - Annotating expression statements with `.discard` and `.ensure_void` | |
| 7 | //! - Flattening nested expressions into instruction sequences. | |
| 8 | //! | |
| 9 | //! Some trivial diagnostics such as unused variables, unknown result locations, and | |
| 10 | //! invalid assignment targets are also reported here. | |
| 2 | 11 | gpa: Allocator, |
| 3 | 12 | ast: Ast, |
| 4 | 13 | insts: Inst.List = .{}, |
| ... | ... | @@ -8,11 +17,6 @@ extra: ExtraStore = .{}, |
| 8 | 17 | scratch: std.ArrayListUnmanaged(u32) = .{}, |
| 9 | 18 | id_decode_buf: std.ArrayList(u8), |
| 10 | 19 | |
| 11 | // TODO: refactor this away | |
| 12 | // The new home for this memory should be pre-allocated on scratch, | |
| 13 | // but then remove the elements, moving scratch items on top of this | |
| 14 | // backwards a few. | |
| 15 | call_args: std.ArrayListUnmanaged(Inst.Ref) = .{}, | |
| 16 | 20 | scopes: std.ArrayListUnmanaged(Scope) = .{}, |
| 17 | 21 | |
| 18 | 22 | report: Report.Builder, |
| ... | ... | @@ -67,7 +71,6 @@ pub fn transform( |
| 67 | 71 | |
| 68 | 72 | pub fn deinit(t: *Transform) void { |
| 69 | 73 | t.scratch.deinit(t.gpa); |
| 70 | t.call_args.deinit(t.gpa); | |
| 71 | 74 | assert(t.scopes.items.len == 0); |
| 72 | 75 | t.scopes.deinit(t.gpa); |
| 73 | 76 | t.string_bytes.deinit(t.gpa); |
| ... | ... | @@ -268,7 +271,7 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I |
| 268 | 271 | s.symbols.lockPointers(); |
| 269 | 272 | defer s.symbols.unlockPointers(); |
| 270 | 273 | for (s.symbols.values(), 0..) |value, i| { |
| 271 | temp[i] = try t.transformFunction(decl, value.node); | |
| 274 | temp[i] = try t.transformDecl(decl, value.node); | |
| 272 | 275 | } |
| 273 | 276 | } |
| 274 | 277 | |
| ... | ... | @@ -316,12 +319,12 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I |
| 316 | 319 | .name = NullTerminatedString.empty, |
| 317 | 320 | .hash = .{0} ** 16, |
| 318 | 321 | .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(top_level_fn), |
| 322 | .body = top_level_code_ref, | |
| 319 | 323 | }, dod.extraSizeOf(Inst.DeclFn)); |
| 320 | 324 | |
| 321 | 325 | trail.writeStruct(Inst.DeclFn, .{ |
| 322 | 326 | .params = .{ .start = 0, .len = 0 }, |
| 323 | 327 | .return_type = Inst.Ref.void, |
| 324 | .body = top_level_code_ref, | |
| 325 | 328 | }); |
| 326 | 329 | |
| 327 | 330 | t.getInstData(top_level_fn).* = EncodedData.encode(.declaration, .{ |
| ... | ... | @@ -346,13 +349,13 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void |
| 346 | 349 | const tag = t.ast.nodes.items(.tag)[node]; |
| 347 | 350 | switch (tag) { |
| 348 | 351 | .fn_decl => { |
| 349 | const fn_decl = t.ast.fullFnDecl(node); | |
| 350 | var id, const id_allocated = try t.decodeIdentifierExtra(fn_decl.id); | |
| 352 | const decl = t.ast.fullFnDecl(node); | |
| 353 | var id, const id_allocated = try t.decodeIdentifierExtra(decl.id); | |
| 351 | 354 | if (id_allocated) |
| 352 | 355 | id = try t.gpa.dupe(u8, id); |
| 353 | 356 | const gop = try t.scope().symbols.getOrPut(t.gpa, id); |
| 354 | 357 | if (gop.found_existing) { |
| 355 | t.gpa.free(id); | |
| 358 | if (id_allocated) t.gpa.free(id); | |
| 356 | 359 | try t.report.err(.duplicate_function, .{ |
| 357 | 360 | .node = node, |
| 358 | 361 | .prev = gop.value_ptr.node, |
| ... | ... | @@ -364,6 +367,22 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void |
| 364 | 367 | .data = .declaration, |
| 365 | 368 | }; |
| 366 | 369 | }, |
| 370 | .struct_decl => { | |
| 371 | const decl = t.ast.fullStructDecl(node); | |
| 372 | const gop = try t.scope().symbols.getOrPut(t.gpa, decl.id); | |
| 373 | if (gop.found_existing) { | |
| 374 | try t.report.err(.duplicate_function, .{ | |
| 375 | .node = node, | |
| 376 | .prev = gop.value_ptr.node, | |
| 377 | // TODO: put type of both declarations | |
| 378 | }); | |
| 379 | return; | |
| 380 | } | |
| 381 | gop.value_ptr.* = .{ | |
| 382 | .node = node, | |
| 383 | .data = .declaration, | |
| 384 | }; | |
| 385 | }, | |
| 367 | 386 | else => {}, |
| 368 | 387 | } |
| 369 | 388 | } |
| ... | ... | @@ -378,12 +397,22 @@ fn transformModuleStatement(t: *Transform, decl: BlockContext, node: NodeIndex) |
| 378 | 397 | .param_decl, |
| 379 | 398 | => unreachable, |
| 380 | 399 | |
| 381 | .fn_decl => {}, // functions analyzed separately | |
| 400 | .fn_decl, | |
| 401 | .struct_decl, | |
| 402 | => {}, // declarations are analyzed separately | |
| 382 | 403 | |
| 383 | 404 | else => _ = try t.transformBlockStatement(decl, node, false), |
| 384 | 405 | } |
| 385 | 406 | } |
| 386 | 407 | |
| 408 | fn transformDecl(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index { | |
| 409 | return switch (t.ast.nodeTag(node)) { | |
| 410 | .fn_decl => t.transformFunction(outer_decl, node), | |
| 411 | .struct_decl => t.transformStruct(outer_decl, node), | |
| 412 | else => unreachable, // not a declaration | |
| 413 | }; | |
| 414 | } | |
| 415 | ||
| 387 | 416 | fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index { |
| 388 | 417 | const trace = tracer.trace(@src()); |
| 389 | 418 | defer trace.end(); |
| ... | ... | @@ -391,7 +420,7 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A |
| 391 | 420 | const decl = outer_decl.declOffset(node); |
| 392 | 421 | const fn_decl = t.ast.fullFnDecl(node); |
| 393 | 422 | |
| 394 | const func = try t.addRootInst(.{ | |
| 423 | const decl_inst = try t.addRootInst(.{ | |
| 395 | 424 | .tag = .declaration, |
| 396 | 425 | .data = undefined, |
| 397 | 426 | }); |
| ... | ... | @@ -421,23 +450,53 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A |
| 421 | 450 | .kind = .function, |
| 422 | 451 | .name = try t.addNullTerminatedString(fn_decl.id), |
| 423 | 452 | .hash = .{0} ** 16, |
| 424 | .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(func), | |
| 453 | .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_inst), | |
| 454 | .body = body, | |
| 425 | 455 | }); |
| 426 | 456 | |
| 427 | 457 | _ = try t.extra.append(t.gpa, Inst.DeclFn, .{ |
| 428 | 458 | .params = param_list, |
| 429 | 459 | .return_type = return_type, |
| 430 | .body = body, | |
| 431 | 460 | }); |
| 432 | 461 | |
| 433 | t.getInstData(func).* = EncodedData.encode(.declaration, .{ | |
| 462 | t.getInstData(decl_inst).* = EncodedData.encode(.declaration, .{ | |
| 434 | 463 | .src_node = outer_decl.nodeOffset(node), |
| 435 | 464 | .extra_index = decl_extra, |
| 436 | 465 | }); |
| 437 | 466 | |
| 438 | 467 | try t.scope().addWarnings(t); |
| 439 | 468 | |
| 440 | return func; | |
| 469 | return decl_inst; | |
| 470 | } | |
| 471 | ||
| 472 | fn transformStruct(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index { | |
| 473 | const trace = tracer.trace(@src()); | |
| 474 | defer trace.end(); | |
| 475 | ||
| 476 | const decl = outer_decl.declOffset(node); | |
| 477 | const decl_data = t.ast.fullStructDecl(node); | |
| 478 | ||
| 479 | const decl_inst = try t.addRootInst(.{ | |
| 480 | .tag = .declaration, | |
| 481 | .data = undefined, | |
| 482 | }); | |
| 483 | ||
| 484 | const body = try t.transformExpressionToBlock(decl, node, false); | |
| 485 | ||
| 486 | const decl_extra = try t.extra.append(t.gpa, Inst.Decl, .{ | |
| 487 | .kind = .immutable, | |
| 488 | .name = try t.addNullTerminatedString(decl_data.id), | |
| 489 | .hash = .{0} ** 16, | |
| 490 | .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_inst), | |
| 491 | .body = body, | |
| 492 | }); | |
| 493 | ||
| 494 | t.getInstData(decl_inst).* = EncodedData.encode(.declaration, .{ | |
| 495 | .src_node = outer_decl.nodeOffset(node), | |
| 496 | .extra_index = decl_extra, | |
| 497 | }); | |
| 498 | ||
| 499 | return decl_inst; | |
| 441 | 500 | } |
| 442 | 501 | |
| 443 | 502 | const TransformFunctionCtx = struct { |
| ... | ... | @@ -555,7 +614,7 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc |
| 555 | 614 | defer trace.end(); |
| 556 | 615 | |
| 557 | 616 | const param_decl = t.ast.fullParamDecl(param); |
| 558 | const value = try t.transformExpressionToBlock(decl, param_decl.type, false); | |
| 617 | const value = try t.transformExpressionToBlock(decl, param_decl.type orelse @panic("TODO: inferred param types"), false); | |
| 559 | 618 | |
| 560 | 619 | var id, const id_allocated = try t.decodeIdentifierExtra(param_decl.id); |
| 561 | 620 | if (id_allocated) |
| ... | ... | @@ -699,7 +758,6 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc |
| 699 | 758 | .root, |
| 700 | 759 | .param_decl, |
| 701 | 760 | .var_decl, |
| 702 | .fn_decl, | |
| 703 | 761 | => unreachable, // not an expression |
| 704 | 762 | |
| 705 | 763 | .id => { |
| ... | ... | @@ -1157,29 +1215,91 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc |
| 1157 | 1215 | return try t.transformExpression(decl, group_node.expr); |
| 1158 | 1216 | }, |
| 1159 | 1217 | |
| 1218 | .struct_decl => { | |
| 1219 | const struct_node = t.ast.fullStructDecl(node); | |
| 1220 | const payload_index = try t.extra.alloc(t.gpa, struct_node.fields.len * 2); | |
| 1221 | ||
| 1222 | for ( | |
| 1223 | struct_node.fields, | |
| 1224 | payload_index.., | |
| 1225 | payload_index + struct_node.fields.len.., | |
| 1226 | ) |field_node, i, j| { | |
| 1227 | const field = t.ast.fullStructField(field_node); | |
| 1228 | ||
| 1229 | if (mem.startsWith(u8, field.id, "@")) { | |
| 1230 | @panic("TODO: string identifiers"); | |
| 1231 | } | |
| 1232 | ||
| 1233 | t.extra.setU32(i, @intFromEnum(try t.addNullTerminatedString(field.id))); | |
| 1234 | t.extra.setU32(j, @intFromEnum(try t.transformExpressionToBlock(decl, field.type, false))); | |
| 1235 | } | |
| 1236 | ||
| 1237 | return try t.addInstRef(.{ | |
| 1238 | .tag = .struct_decl, | |
| 1239 | .data = EncodedData.encode(.struct_decl, .{ | |
| 1240 | .src_node = decl.nodeOffset(node), | |
| 1241 | .extra_index = try t.extra.append(t.gpa, Inst.StructDecl, .{ | |
| 1242 | .payload_index = payload_index, | |
| 1243 | .field_len = @intCast(struct_node.fields.len), | |
| 1244 | }), | |
| 1245 | }), | |
| 1246 | }); | |
| 1247 | }, | |
| 1248 | ||
| 1249 | .struct_init => { | |
| 1250 | const struct_node = t.ast.fullStructInit(node); | |
| 1251 | const payload_index = try t.extra.alloc(t.gpa, struct_node.fields.len * 2); | |
| 1252 | ||
| 1253 | const base_type = try t.transformExpression( | |
| 1254 | decl, | |
| 1255 | struct_node.type orelse @panic("TODO: TEG implicit struct"), | |
| 1256 | ) orelse | |
| 1257 | @panic("TODO: print better error"); | |
| 1258 | ||
| 1259 | for ( | |
| 1260 | struct_node.fields, | |
| 1261 | payload_index.., | |
| 1262 | payload_index + struct_node.fields.len.., | |
| 1263 | ) |field_node, i, j| { | |
| 1264 | const field = t.ast.fullStructField(field_node); | |
| 1265 | ||
| 1266 | if (mem.startsWith(u8, field.id, "@")) { | |
| 1267 | @panic("TODO: string identifiers"); | |
| 1268 | } | |
| 1269 | ||
| 1270 | t.extra.setU32(i, @intFromEnum(try t.addNullTerminatedString(field.id))); | |
| 1271 | t.extra.setU32(j, @intFromEnum((try t.transformExpression(decl, field.type)) orelse | |
| 1272 | @panic("TODO: print better error"))); | |
| 1273 | } | |
| 1274 | ||
| 1275 | return try t.addInstRef(.{ | |
| 1276 | .tag = .struct_init, | |
| 1277 | .data = EncodedData.encode(.struct_init, .{ | |
| 1278 | .src_node = decl.nodeOffset(node), | |
| 1279 | .extra_index = try t.extra.append(t.gpa, Inst.StructInit, .{ | |
| 1280 | .base_type = base_type, | |
| 1281 | .payload_index = payload_index, | |
| 1282 | .field_len = @intCast(struct_node.fields.len), | |
| 1283 | }), | |
| 1284 | }), | |
| 1285 | }); | |
| 1286 | }, | |
| 1287 | ||
| 1160 | 1288 | else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}), |
| 1161 | 1289 | } |
| 1162 | 1290 | } |
| 1163 | 1291 | |
| 1164 | 1292 | fn transformCallArgs(t: *Transform, decl: BlockContext, args: []const NodeIndex) !?RefList { |
| 1165 | const args_top = t.call_args.items.len; | |
| 1166 | defer t.call_args.shrinkRetainingCapacity(args_top); | |
| 1167 | ||
| 1168 | for (args) |arg| { | |
| 1169 | try t.call_args.append( | |
| 1170 | t.gpa, | |
| 1171 | try t.transformExpression(decl, arg) orelse | |
| 1172 | return null, | |
| 1173 | ); | |
| 1174 | } | |
| 1293 | const extra_top = try t.extra.alloc(t.gpa, args.len); | |
| 1175 | 1294 | |
| 1176 | const items = t.call_args.items[args_top..]; | |
| 1177 | const extra_top = t.extra.list.items.len; | |
| 1178 | try t.extra.list.appendSlice(t.gpa, @ptrCast(items)); | |
| 1295 | for (args, extra_top..) |arg, i| { | |
| 1296 | t.extra.setU32(i, @intFromEnum(try t.transformExpression(decl, arg) orelse | |
| 1297 | return null)); | |
| 1298 | } | |
| 1179 | 1299 | |
| 1180 | 1300 | return .{ |
| 1181 | 1301 | .start = @intCast(extra_top), |
| 1182 | .len = @intCast(items.len), | |
| 1302 | .len = @intCast(args.len), | |
| 1183 | 1303 | }; |
| 1184 | 1304 | } |
| 1185 | 1305 | |
| ... | ... | @@ -1193,7 +1313,6 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex) |
| 1193 | 1313 | .root, |
| 1194 | 1314 | .param_decl, |
| 1195 | 1315 | .var_decl, |
| 1196 | .fn_decl, | |
| 1197 | 1316 | => unreachable, // not an expression |
| 1198 | 1317 | |
| 1199 | 1318 | .id => { |
src/core/array.zig+63-28| ... | ... | @@ -40,44 +40,46 @@ fn setAsU32(items: []u32, comptime T: type, index: usize, payload: T) void { |
| 40 | 40 | } |
| 41 | 41 | |
| 42 | 42 | fn getAsU32(items: []const u32, comptime T: type, index: usize) T { |
| 43 | const fields = std.meta.fields(T); | |
| 44 | var i = index; | |
| 45 | var result: T = undefined; | |
| 46 | inline for (fields) |field| { | |
| 47 | @field(result, field.name) = switch (field.type) { | |
| 48 | u32 => items[i], | |
| 49 | i32 => @bitCast(items[i]), | |
| 43 | return switch (T) { | |
| 44 | u32 => items[index], | |
| 45 | i32 => @bitCast(items[index]), | |
| 50 | 46 | |
| 51 | [16]u8 => @bitCast(items[i..][0..4].*), | |
| 47 | [16]u8 => @bitCast(items[index..][0..4].*), | |
| 52 | 48 | |
| 53 | else => brk: { | |
| 54 | const info = @typeInfo(field.type); | |
| 55 | if (info == .Enum) { | |
| 56 | break :brk @enumFromInt(items[i]); | |
| 57 | } | |
| 49 | else => brk: { | |
| 50 | const info = @typeInfo(T); | |
| 51 | if (info == .Enum) { | |
| 52 | break :brk @enumFromInt(items[index]); | |
| 53 | } | |
| 58 | 54 | |
| 59 | const struct_data = info.Struct; | |
| 60 | if (struct_data.layout == .@"packed") { | |
| 61 | break :brk @bitCast(items[i]); | |
| 62 | } | |
| 55 | const struct_data = info.Struct; | |
| 56 | if (struct_data.layout == .@"packed") { | |
| 57 | break :brk @bitCast(items[index]); | |
| 58 | } | |
| 63 | 59 | |
| 64 | break :brk @call( | |
| 60 | const fields = std.meta.fields(T); | |
| 61 | var i = index; | |
| 62 | var result: T = undefined; | |
| 63 | ||
| 64 | inline for (fields) |field| { | |
| 65 | @field(result, field.name) = @call( | |
| 65 | 66 | .always_inline, |
| 66 | 67 | getAsU32, |
| 67 | 68 | .{ items, field.type, i }, |
| 68 | 69 | ); |
| 69 | }, | |
| 70 | }; | |
| 71 | i += extraSizeOf(field.type); | |
| 72 | } | |
| 73 | return result; | |
| 70 | i += comptime extraSizeOf(field.type); | |
| 71 | } | |
| 72 | ||
| 73 | break :brk result; | |
| 74 | }, | |
| 75 | }; | |
| 74 | 76 | } |
| 75 | 77 | |
| 76 | 78 | pub const StoreKind = enum { |
| 77 | 79 | intern_pool, |
| 78 | 80 | teg, |
| 79 | 81 | air, |
| 80 | tfr, | |
| 82 | js_tree, | |
| 81 | 83 | |
| 82 | 84 | pub fn resolveStoreConst( |
| 83 | 85 | comptime kind: StoreKind, |
| ... | ... | @@ -204,6 +206,7 @@ pub fn extraSizeOf(comptime T: type) usize { |
| 204 | 206 | }; |
| 205 | 207 | } |
| 206 | 208 | |
| 209 | // TODO: rename to Store | |
| 207 | 210 | pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutability) type { |
| 208 | 211 | return struct { |
| 209 | 212 | list: List = .{}, |
| ... | ... | @@ -240,7 +243,11 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil |
| 240 | 243 | return ip.get(T, trail.index); |
| 241 | 244 | } |
| 242 | 245 | |
| 243 | pub fn readValueSlice(trail: *TrailReader, len: u32) @import("InternPool.zig").Value.Slice { | |
| 246 | pub fn at(trail: *const TrailReader, ip: *const Store, comptime T: type, index: u32) T { | |
| 247 | return ip.get(T, trail.index + index); | |
| 248 | } | |
| 249 | ||
| 250 | pub fn readValueSlice(trail: *TrailReader, len: u32) InternPool.Value.Slice { | |
| 244 | 251 | defer trail.index += len; |
| 245 | 252 | return .{ |
| 246 | 253 | .start = @intCast(trail.index), |
| ... | ... | @@ -248,7 +255,18 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil |
| 248 | 255 | }; |
| 249 | 256 | } |
| 250 | 257 | |
| 251 | pub fn readTypeSlice(trail: *TrailReader, len: u32) @import("InternPool.zig").Type.Slice { | |
| 258 | pub fn readTypeSlice(trail: *TrailReader, len: u32) InternPool.Type.Slice { | |
| 259 | defer trail.index += len; | |
| 260 | return .{ | |
| 261 | .start = @intCast(trail.index), | |
| 262 | .len = len, | |
| 263 | }; | |
| 264 | } | |
| 265 | ||
| 266 | pub fn readNullTerminatedStringSlice( | |
| 267 | trail: *TrailReader, | |
| 268 | len: u32, | |
| 269 | ) InternPool.NullTerminatedString.Slice { | |
| 252 | 270 | defer trail.index += len; |
| 253 | 271 | return .{ |
| 254 | 272 | .start = @intCast(trail.index), |
| ... | ... | @@ -321,12 +339,18 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil |
| 321 | 339 | }; |
| 322 | 340 | } |
| 323 | 341 | |
| 324 | fn set(store: *Store, comptime T: type, index: usize, payload: T) void { | |
| 342 | // TODO: make setAsU32 allow any type | |
| 343 | pub fn setU32(store: *Store, index: usize, payload: u32) void { | |
| 344 | comptime assert(mutability == .@"var"); | |
| 345 | store.list.items[index] = payload; | |
| 346 | } | |
| 347 | ||
| 348 | pub fn set(store: *Store, comptime T: type, index: usize, payload: T) void { | |
| 325 | 349 | comptime assert(mutability == .@"var"); |
| 326 | 350 | setAsU32(store.list.items, T, index, payload); |
| 327 | 351 | } |
| 328 | 352 | |
| 329 | fn get(store: *const Store, comptime T: type, index: usize) T { | |
| 353 | pub fn get(store: *const Store, comptime T: type, index: usize) T { | |
| 330 | 354 | return getAsU32(store.list.items, T, index); |
| 331 | 355 | } |
| 332 | 356 | |
| ... | ... | @@ -338,6 +362,16 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil |
| 338 | 362 | pub fn constant(store: *const Store) ExtraStore(set_kind, .@"const") { |
| 339 | 363 | return .{ .list = .{ .items = store.list.items } }; |
| 340 | 364 | } |
| 365 | ||
| 366 | // TODO: Add some safety to the Index returned from this | |
| 367 | /// Allocate a number of u32s into this store, but they are left uninitialized. | |
| 368 | pub fn alloc(store: *Store, gpa: Allocator, len: usize) !u32 { | |
| 369 | comptime assert(mutability == .@"var"); | |
| 370 | const idx = store.list.items.len; | |
| 371 | try store.list.ensureUnusedCapacity(gpa, len); | |
| 372 | store.list.items.len += len; | |
| 373 | return @intCast(idx); | |
| 374 | } | |
| 341 | 375 | }; |
| 342 | 376 | } |
| 343 | 377 | |
| ... | ... | @@ -388,3 +422,4 @@ pub const Float = struct { |
| 388 | 422 | const std = @import("std"); |
| 389 | 423 | const assert = std.debug.assert; |
| 390 | 424 | const Allocator = std.mem.Allocator; |
| 425 | const InternPool = @import("InternPool.zig"); |
src/core/js/Js.zig created+65| ... | ... | @@ -0,0 +1,65 @@ |
| 1 | //! JavaScript code generation backend | |
| 2 | //! | |
| 3 | //! * Air -(NodeGen)-> JsTree | |
| 4 | //! * []JsTree -(Link)-> []const u8 | |
| 5 | //! | |
| 6 | pub const NodeGen = @import("NodeGen.zig"); | |
| 7 | pub const Tree = @import("Tree.zig"); | |
| 8 | pub const Link = @import("Link.zig"); | |
| 9 | ||
| 10 | codegen_map: std.AutoHashMapUnmanaged(Value, Tree) = .{}, | |
| 11 | ||
| 12 | pub fn deinit(backend: *Js, comp: *Comp) void { | |
| 13 | { | |
| 14 | var it = backend.codegen_map.valueIterator(); | |
| 15 | while (it.next()) |item| { | |
| 16 | item.deinit(comp.gpa); | |
| 17 | } | |
| 18 | } | |
| 19 | ||
| 20 | backend.codegen_map.deinit(comp.gpa); | |
| 21 | } | |
| 22 | ||
| 23 | pub fn handleFunctionAir( | |
| 24 | backend: *Js, | |
| 25 | comp: *Comp, | |
| 26 | value: Value, | |
| 27 | air: *const Air, | |
| 28 | arena: Allocator, | |
| 29 | ) !u32 { | |
| 30 | var ng = try NodeGen.init(comp.gpa, arena, &comp.intern_pool, air); | |
| 31 | errdefer ng.deinit(); | |
| 32 | try ng.build(); | |
| 33 | ||
| 34 | const tree = try ng.finalize(); | |
| 35 | try backend.codegen_map.put(comp.gpa, value, tree); | |
| 36 | ||
| 37 | return 0; | |
| 38 | } | |
| 39 | ||
| 40 | pub fn link( | |
| 41 | backend: *Js, | |
| 42 | comp: *Comp, | |
| 43 | entry_point: Comp.Decl.Index, | |
| 44 | writer: std.io.AnyWriter, | |
| 45 | ) !void { | |
| 46 | var state = Link{ | |
| 47 | .comp = comp, | |
| 48 | .backend = backend, | |
| 49 | }; | |
| 50 | defer state.deinit(); | |
| 51 | try state.linkJsCode(entry_point, writer); | |
| 52 | } | |
| 53 | ||
| 54 | const Js = @This(); | |
| 55 | ||
| 56 | const std = @import("std"); | |
| 57 | const assert = std.debug.assert; | |
| 58 | const log = std.log.scoped(.js); | |
| 59 | const Allocator = std.mem.Allocator; | |
| 60 | ||
| 61 | const core = @import("../mod.zig"); | |
| 62 | const Comp = core.Comp; | |
| 63 | const Air = core.Air; | |
| 64 | const InternPool = core.InternPool; | |
| 65 | const Value = InternPool.Value; |
src/core/js/Link.zig created+821| ... | ... | @@ -0,0 +1,821 @@ |
| 1 | //! This is the JavaScript backend's linker. It takes in a set of `Js.Tree`s and | |
| 2 | //! produces a JavaScript bundle out of them. | |
| 3 | //! | |
| 4 | //! This approach is flawed because it combining too much work in one unit. | |
| 5 | //! Printing and Linking should be separate stages, so that more work can be | |
| 6 | //! done in parallel. This approach is used for now to make identifier name | |
| 7 | //! collisions easier to deal with. | |
| 8 | comp: *Comp, | |
| 9 | backend: *Js, | |
| 10 | ||
| 11 | /// Value is an index into `link_strings` | |
| 12 | decls: std.AutoHashMapUnmanaged(InternPool.Value, LinkString.Index) = .{}, | |
| 13 | link_strings: std.ArrayHashMapUnmanaged( | |
| 14 | LinkString, | |
| 15 | GlobalOwner, | |
| 16 | LinkString.Context, | |
| 17 | true, | |
| 18 | ) = .{}, | |
| 19 | string_bytes: std.ArrayListUnmanaged(u8) = .{}, | |
| 20 | ||
| 21 | pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void { | |
| 22 | var t = tracer.trace(@src()); | |
| 23 | defer t.end(); | |
| 24 | ||
| 25 | // 1. Resolve all global identifiers. Function names must not shadow external | |
| 26 | // names. We also need this list prepared so that local identifier names do not | |
| 27 | // shadow functions or externals. | |
| 28 | const root_decl = link.comp.intern_pool.declPtrConst(entry_decl); | |
| 29 | assert(root_decl.name == .empty); | |
| 30 | ||
| 31 | const root_function = link.treeForFunction(root_decl.value()); | |
| 32 | ||
| 33 | for (root_function.extern_symbols) |extern_symbol| { | |
| 34 | try link.reserveExternSymbol(extern_symbol); | |
| 35 | } | |
| 36 | ||
| 37 | var dependency_queue: DependencyQueue = .{}; | |
| 38 | defer dependency_queue.list.deinit(link.comp.gpa); | |
| 39 | try dependency_queue.list.appendSlice(link.comp.gpa, root_function.top_level_value_deps); | |
| 40 | ||
| 41 | while (dependency_queue.next()) |value| { | |
| 42 | const tree = link.treeForFunction(value); | |
| 43 | ||
| 44 | for (tree.extern_symbols) |extern_symbol| { | |
| 45 | try link.reserveExternSymbol(extern_symbol); | |
| 46 | } | |
| 47 | ||
| 48 | try dependency_queue.list.appendSlice(link.comp.gpa, tree.top_level_value_deps); | |
| 49 | ||
| 50 | const decl = link.comp.intern_pool.declPtrConst( | |
| 51 | value.valueData(&link.comp.intern_pool, .func) | |
| 52 | .get(&link.comp.intern_pool) | |
| 53 | .owner_decl, | |
| 54 | ); | |
| 55 | const name = decl.name.toSlice(&link.comp.intern_pool); | |
| 56 | assert(name.len > 0); | |
| 57 | ||
| 58 | const gop = try link.link_strings.getOrPutContextAdapted( | |
| 59 | link.comp.gpa, | |
| 60 | name, | |
| 61 | LinkString.SliceContext{ .link = link }, | |
| 62 | .{ .link = link }, | |
| 63 | ); | |
| 64 | if (gop.found_existing) { | |
| 65 | @panic("TODO: handle this name collision case"); | |
| 66 | } | |
| 67 | const name_str = try link.appendLinkString(name); | |
| 68 | gop.key_ptr.* = name_str; | |
| 69 | gop.value_ptr.* = .decl; | |
| 70 | try link.decls.putNoClobber(link.comp.gpa, value, @enumFromInt(gop.index)); | |
| 71 | } | |
| 72 | ||
| 73 | try writer.print("// Language Compiler v0.1.4. Alpha quality software.\n", .{}); | |
| 74 | ||
| 75 | if (root_function.nodes.len > 1) { | |
| 76 | // 2. Print every function decl | |
| 77 | dependency_queue.i = 0; | |
| 78 | while (dependency_queue.next()) |value| { | |
| 79 | try link.printTopLevelValue(value, writer); | |
| 80 | } | |
| 81 | ||
| 82 | // 3. Print the top-level code | |
| 83 | try link.printDeclContents(root_function, writer); | |
| 84 | } else { | |
| 85 | try writer.print("// (program has no observable effects, thus an empty file)\n", .{}); | |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | fn treeForFunction(link: *const Link, value: InternPool.Value) *Tree { | |
| 90 | assert(value.valueTag(&link.comp.intern_pool) == .func); // must be a function | |
| 91 | return link.backend.codegen_map.getPtr(value) orelse unreachable; | |
| 92 | } | |
| 93 | ||
| 94 | const DependencyQueue = struct { | |
| 95 | list: std.ArrayListUnmanaged(InternPool.Value) = .{}, | |
| 96 | i: usize = 0, | |
| 97 | ||
| 98 | pub fn next(queue: *DependencyQueue) ?InternPool.Value { | |
| 99 | if (queue.i >= queue.list.items.len) { | |
| 100 | return null; | |
| 101 | } | |
| 102 | ||
| 103 | defer queue.i += 1; | |
| 104 | return queue.list.items[queue.i]; | |
| 105 | } | |
| 106 | }; | |
| 107 | ||
| 108 | pub fn deinit(link: *Link) void { | |
| 109 | var t = tracer.trace(@src()); | |
| 110 | defer t.end(); | |
| 111 | ||
| 112 | defer link.* = undefined; | |
| 113 | link.decls.deinit(link.comp.gpa); | |
| 114 | link.link_strings.deinit(link.comp.gpa); | |
| 115 | link.string_bytes.deinit(link.comp.gpa); | |
| 116 | } | |
| 117 | ||
| 118 | fn reserveExternSymbol(link: *Link, extern_symbol: String.WithLen) !void { | |
| 119 | var t = tracer.trace(@src()); | |
| 120 | defer t.end(); | |
| 121 | ||
| 122 | const name = extern_symbol.toSlice(&link.comp.intern_pool); | |
| 123 | const gop = try link.link_strings.getOrPutContextAdapted( | |
| 124 | link.comp.gpa, | |
| 125 | name, | |
| 126 | LinkString.SliceContext{ .link = link }, | |
| 127 | .{ .link = link }, | |
| 128 | ); | |
| 129 | ||
| 130 | if (gop.found_existing) { | |
| 131 | if (gop.value_ptr.* != .external) { | |
| 132 | @panic("TODO: Rename Decl"); | |
| 133 | } | |
| 134 | } | |
| 135 | gop.key_ptr.* = try link.appendLinkString(name); | |
| 136 | gop.value_ptr.* = .external; | |
| 137 | } | |
| 138 | ||
| 139 | fn appendLinkString(link: *Link, string: []const u8) !LinkString { | |
| 140 | var t = tracer.trace(@src()); | |
| 141 | defer t.end(); | |
| 142 | ||
| 143 | const start = link.string_bytes.items.len; | |
| 144 | try link.string_bytes.appendSlice(link.comp.gpa, string); | |
| 145 | return .{ .start = @intCast(start), .len = @intCast(string.len) }; | |
| 146 | } | |
| 147 | ||
| 148 | fn valueRefIdentifier(link: *const Link, value: InternPool.Value) []const u8 { | |
| 149 | var t = tracer.trace(@src()); | |
| 150 | defer t.end(); | |
| 151 | ||
| 152 | return (link.decls.get(value) orelse if (std.debug.runtime_safety) { | |
| 153 | std.debug.panic("Value \"{}\" is not present in top level value map", .{ | |
| 154 | value.dump(&link.comp.intern_pool), | |
| 155 | }); | |
| 156 | } else unreachable).toString(link).slice(link); | |
| 157 | } | |
| 158 | ||
| 159 | fn printTopLevelValue(link: *Link, value: InternPool.Value, writer: Writer) !void { | |
| 160 | var t = tracer.trace(@src()); | |
| 161 | defer t.end(); | |
| 162 | ||
| 163 | try writer.writeAll("let "); | |
| 164 | try writer.writeAll(link.valueRefIdentifier(value)); | |
| 165 | try writer.writeAll(" = "); | |
| 166 | ||
| 167 | const tree = link.treeForFunction(value); | |
| 168 | ||
| 169 | if (tree.param_count != 1) | |
| 170 | try writer.writeAll("("); | |
| 171 | for (0..tree.param_count) |id| { | |
| 172 | try link.printLocal(@intCast(id), writer); | |
| 173 | if (id != tree.param_count - 1) try writer.writeAll(", "); | |
| 174 | } | |
| 175 | if (tree.param_count != 1) | |
| 176 | try writer.writeAll(")"); | |
| 177 | try writer.writeAll(" => "); | |
| 178 | ||
| 179 | const stmts = tree.mainStatements(); | |
| 180 | if (stmts.len == 1 and tree.nodeTag(stmts[0]) == .stmt_return) { | |
| 181 | if (tree.nodeData(.stmt_return, stmts[0]).operand.unwrap()) |operand| { | |
| 182 | try link.printExprLevel(tree, operand, .statement, writer); | |
| 183 | } else { | |
| 184 | try writer.writeAll("{}"); | |
| 185 | } | |
| 186 | } else { | |
| 187 | try writer.writeAll("{\n"); | |
| 188 | try link.printDeclContents(tree, writer); | |
| 189 | try writer.writeAll("}"); | |
| 190 | } | |
| 191 | ||
| 192 | try writer.writeAll(";\n"); | |
| 193 | } | |
| 194 | ||
| 195 | fn printDeclContents(link: *Link, tree: *const Tree, writer: Writer) !void { | |
| 196 | var t = tracer.trace(@src()); | |
| 197 | defer t.end(); | |
| 198 | ||
| 199 | for (tree.mainStatements()) |stmt| { | |
| 200 | try link.printExprLevel(tree, stmt, .statement, writer); | |
| 201 | try writer.writeAll(";\n"); | |
| 202 | } | |
| 203 | } | |
| 204 | ||
| 205 | fn printBlockContents( | |
| 206 | link: *Link, | |
| 207 | tree: *const Tree, | |
| 208 | list: Tree.IndexList, | |
| 209 | writer: Writer, | |
| 210 | ) !void { | |
| 211 | var t = tracer.trace(@src()); | |
| 212 | defer t.end(); | |
| 213 | ||
| 214 | assert(list.slice(tree).len > 0); | |
| 215 | for (list.slice(tree)) |stmt| { | |
| 216 | try link.printExpr(tree, stmt, writer); | |
| 217 | switch (tree.nodeTag(stmt)) { | |
| 218 | .stmt_block, | |
| 219 | .stmt_if, | |
| 220 | => {}, | |
| 221 | else => try writer.writeAll(";"), | |
| 222 | } | |
| 223 | try writer.writeAll("\n"); | |
| 224 | } | |
| 225 | } | |
| 226 | ||
| 227 | fn printBlock(link: *Link, tree: *const Tree, list: Tree.IndexList, writer: Writer) !void { | |
| 228 | var t = tracer.trace(@src()); | |
| 229 | defer t.end(); | |
| 230 | ||
| 231 | if (list.len != 1) { | |
| 232 | try writer.writeAll("{"); | |
| 233 | try link.printBlockContents(tree, list, writer); | |
| 234 | try writer.writeAll("}"); | |
| 235 | } else { | |
| 236 | try link.printBlockContents(tree, list, writer); | |
| 237 | } | |
| 238 | } | |
| 239 | ||
| 240 | fn printExpr( | |
| 241 | link: *Link, | |
| 242 | tree: *const Tree, | |
| 243 | node: Node.Index, | |
| 244 | writer: Writer, | |
| 245 | ) @TypeOf(writer).Error!void { | |
| 246 | return link.printExprLevel(tree, node, .comma, writer); | |
| 247 | } | |
| 248 | ||
| 249 | fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytype) !void { | |
| 250 | switch (value.toKey(&link.comp.intern_pool)) { | |
| 251 | // TODO: minification | |
| 252 | .int32 => |val| try writer.print("{d}", .{val}), | |
| 253 | // TODO: correct printing of many value types | |
| 254 | .float => |val| try writer.print("{d}", .{val}), | |
| 255 | .simple_value => |val| try writer.writeAll(switch (val) { | |
| 256 | .true => "true", | |
| 257 | .false => "false", | |
| 258 | // TODO: make codegen decide from null and undefined | |
| 259 | // instead of emitting void nodes. | |
| 260 | .void => "/* void */ undefined", | |
| 261 | }), | |
| 262 | .string => |val| try writeString(val, writer), | |
| 263 | ||
| 264 | .comptime_float => unreachable, // was not lowered to float | |
| 265 | ||
| 266 | .struct_value => |struct_value| { | |
| 267 | const wrap = level == .statement; | |
| 268 | if (wrap) | |
| 269 | try writer.writeAll("("); | |
| 270 | ||
| 271 | try writer.writeAll("{"); | |
| 272 | ||
| 273 | for ( | |
| 274 | struct_value.ty.toStructType(&link.comp.intern_pool).?.field_keys, | |
| 275 | struct_value.field_values, | |
| 276 | 0.., | |
| 277 | ) |key, inner_value, i| { | |
| 278 | if (i != 0) | |
| 279 | try writer.writeAll(","); | |
| 280 | ||
| 281 | // TODO: handle special identifiers | |
| 282 | try writer.writeAll(key.toSlice(&link.comp.intern_pool)); | |
| 283 | try writer.writeAll(":"); | |
| 284 | try link.printValue(inner_value, .comma, writer); | |
| 285 | } | |
| 286 | try writer.writeAll("}"); | |
| 287 | ||
| 288 | if (wrap) | |
| 289 | try writer.writeAll(")"); | |
| 290 | }, | |
| 291 | ||
| 292 | else => unreachable, // this value is not a plain_value | |
| 293 | } | |
| 294 | } | |
| 295 | ||
| 296 | fn printExprLevel( | |
| 297 | link: *Link, | |
| 298 | tree: *const Tree, | |
| 299 | node: Node.Index, | |
| 300 | level: Level, | |
| 301 | writer: Writer, | |
| 302 | ) @TypeOf(writer).Error!void { | |
| 303 | var t = tracer.trace(@src()); | |
| 304 | defer t.end(); | |
| 305 | ||
| 306 | switch (tree.nodeTag(node)) { | |
| 307 | .root => unreachable, | |
| 308 | ||
| 309 | .plain_value => { | |
| 310 | const plain_value: Tree.PlainValue = tree.nodeData(.plain_value, node); | |
| 311 | try link.printValue(plain_value.value, level, writer); | |
| 312 | }, | |
| 313 | ||
| 314 | .local => { | |
| 315 | const ref: Tree.LocalRef = tree.nodeData(.local, node); | |
| 316 | try link.printLocal(ref.id, writer); | |
| 317 | }, | |
| 318 | ||
| 319 | .global => { | |
| 320 | const ref: Tree.GlobalRef = tree.nodeData(.global, node); | |
| 321 | try writer.writeAll(ref.name.toSlice(&link.comp.intern_pool)); | |
| 322 | }, | |
| 323 | ||
| 324 | .top_level_value => { | |
| 325 | const ref: Tree.ValueRef = tree.nodeData(.top_level_value, node); | |
| 326 | try writer.writeAll(link.valueRefIdentifier(ref.value)); | |
| 327 | }, | |
| 328 | ||
| 329 | .stmt_break => { | |
| 330 | const stmt: Tree.StmtBreak = tree.nodeData(.stmt_break, node); | |
| 331 | try writer.writeAll("break"); | |
| 332 | if (true) { | |
| 333 | try writer.writeAll(" "); | |
| 334 | try link.printLabelIdentifier(stmt.label, writer); | |
| 335 | } | |
| 336 | }, | |
| 337 | ||
| 338 | .stmt_return => { | |
| 339 | const stmt: Tree.StmtReturn = tree.nodeData(.stmt_return, node); | |
| 340 | try writer.writeAll("return"); | |
| 341 | if (stmt.operand.unwrap()) |operand| { | |
| 342 | try writer.writeAll(" "); | |
| 343 | try link.printExpr(tree, operand, writer); | |
| 344 | } | |
| 345 | }, | |
| 346 | ||
| 347 | .stmt_declare_local => { | |
| 348 | const stmt: Tree.StmtDeclareLocal = tree.nodeData(.stmt_declare_local, node); | |
| 349 | try writer.writeAll("let "); | |
| 350 | try link.printLocal(stmt.identifier_index, writer); | |
| 351 | if (stmt.init.unwrap()) |init| { | |
| 352 | try writer.writeAll(" = "); | |
| 353 | try link.printExpr(tree, init, writer); | |
| 354 | } | |
| 355 | }, | |
| 356 | ||
| 357 | .stmt_if => { | |
| 358 | const stmt: Tree.StmtIf = tree.nodeData(.stmt_if, node); | |
| 359 | const data = stmt.data.get(tree); | |
| 360 | try writer.writeAll("if ("); | |
| 361 | try link.printExpr(tree, stmt.cond, writer); | |
| 362 | try writer.writeAll(") "); | |
| 363 | if (data.flags.yes_label) { | |
| 364 | try link.printLabelIdentifier(data.flags.block_label, writer); | |
| 365 | try writer.writeAll(":"); | |
| 366 | } | |
| 367 | try link.printBlock(tree, data.yes, writer); | |
| 368 | if (data.no.len > 0) { | |
| 369 | try writer.writeAll(" else "); | |
| 370 | if (data.flags.no_label) { | |
| 371 | try link.printLabelIdentifier(data.flags.block_label, writer); | |
| 372 | try writer.writeAll(":"); | |
| 373 | } | |
| 374 | try link.printBlock(tree, data.no, writer); | |
| 375 | } | |
| 376 | }, | |
| 377 | ||
| 378 | .stmt_block => { | |
| 379 | const stmt: Tree.StmtBlock = tree.nodeData(.stmt_block, node); | |
| 380 | try link.printLabelIdentifier(stmt.label, writer); | |
| 381 | try writer.writeAll(": {\n"); | |
| 382 | try link.printBlockContents(tree, stmt.content.get(tree), writer); | |
| 383 | try writer.writeAll("}"); | |
| 384 | }, | |
| 385 | ||
| 386 | inline .assign, | |
| 387 | .add, | |
| 388 | .sub, | |
| 389 | .mul, | |
| 390 | .div, | |
| 391 | .eq, | |
| 392 | .lt, | |
| 393 | .gt, | |
| 394 | .assign_add, | |
| 395 | .assign_sub, | |
| 396 | .assign_mul, | |
| 397 | .assign_div, | |
| 398 | => |tag| { | |
| 399 | const bin: Tree.Bin = tree.nodeData(tag, node); | |
| 400 | const op: Operator = @field(Operator, @tagName(tag)); | |
| 401 | const inner_level = comptime op.level(); | |
| 402 | if (inner_level.needsParens(level)) | |
| 403 | try writer.writeAll("("); | |
| 404 | try link.printExprLevel(tree, bin.lhs, inner_level, writer); | |
| 405 | try writer.writeAll(" "); | |
| 406 | try writer.writeAll(comptime op.symbol()); | |
| 407 | try writer.writeAll(" "); | |
| 408 | try link.printExprLevel(tree, bin.rhs, inner_level, writer); | |
| 409 | if (inner_level.needsParens(level)) | |
| 410 | try writer.writeAll(")"); | |
| 411 | }, | |
| 412 | ||
| 413 | inline .negate, | |
| 414 | .bool_not, | |
| 415 | .pre_dec, | |
| 416 | .pre_inc, | |
| 417 | => |tag| { | |
| 418 | const un: Tree.Un = tree.nodeData(tag, node); | |
| 419 | const op: Operator = @field(Operator, @tagName(tag)); | |
| 420 | try writer.writeAll(comptime op.symbol()); | |
| 421 | try link.printExpr(tree, un.operand, writer); | |
| 422 | }, | |
| 423 | ||
| 424 | .ternary => { | |
| 425 | const ternary: Tree.Ternary = tree.nodeData(.ternary, node); | |
| 426 | const data = ternary.data.get(tree); | |
| 427 | try writer.writeAll("("); | |
| 428 | try link.printExpr(tree, ternary.cond, writer); | |
| 429 | try writer.writeAll(" ? "); | |
| 430 | try link.printExpr(tree, data.yes, writer); | |
| 431 | try writer.writeAll(" : "); | |
| 432 | try link.printExpr(tree, data.no, writer); | |
| 433 | try writer.writeAll(")"); | |
| 434 | }, | |
| 435 | ||
| 436 | .call => { | |
| 437 | const call: Tree.Call = tree.nodeData(.call, node); | |
| 438 | const args = call.args.get(tree); | |
| 439 | ||
| 440 | try link.printExpr(tree, call.base, writer); | |
| 441 | try writer.writeAll("("); | |
| 442 | ||
| 443 | for (args.slice(tree), 0..) |arg, i| { | |
| 444 | if (i != 0) try writer.writeAll(", "); | |
| 445 | try link.printExpr(tree, arg, writer); | |
| 446 | } | |
| 447 | ||
| 448 | try writer.writeAll(")"); | |
| 449 | }, | |
| 450 | .new => { | |
| 451 | const call: Tree.Call = tree.nodeData(.new, node); | |
| 452 | const args = call.args.get(tree); | |
| 453 | ||
| 454 | if (Level.new.needsParens(level)) | |
| 455 | try writer.writeAll("("); | |
| 456 | try writer.writeAll("new "); | |
| 457 | try link.printExpr(tree, call.base, writer); | |
| 458 | ||
| 459 | if (args.len > 0) { | |
| 460 | try writer.writeAll("("); | |
| 461 | ||
| 462 | for (args.slice(tree), 0..) |arg, i| { | |
| 463 | if (i != 0) try writer.writeAll(", "); | |
| 464 | try link.printExpr(tree, arg, writer); | |
| 465 | } | |
| 466 | ||
| 467 | try writer.writeAll(")"); | |
| 468 | } | |
| 469 | if (Level.new.needsParens(level)) | |
| 470 | try writer.writeAll(")"); | |
| 471 | }, | |
| 472 | ||
| 473 | .field => { | |
| 474 | const field: Tree.Field = tree.nodeData(.field, node); | |
| 475 | ||
| 476 | try link.printExpr(tree, field.base, writer); | |
| 477 | try writer.writeAll("."); | |
| 478 | try writer.writeAll(field.field.toSlice(&link.comp.intern_pool)); | |
| 479 | }, | |
| 480 | ||
| 481 | .object_literal => { | |
| 482 | const obj: Tree.ObjectLiteral = tree.nodeData(.object_literal, node); | |
| 483 | ||
| 484 | const keys: []const Tree.ObjectLiteral.Key = | |
| 485 | @ptrCast(tree.extra.list.items[obj.extra_index..][0..obj.property_count]); | |
| 486 | const values: []const Node.Index = | |
| 487 | @ptrCast(tree.extra.list.items[obj.extra_index + obj.property_count ..][0..obj.property_count]); | |
| 488 | ||
| 489 | if (level == .statement) | |
| 490 | try writer.writeAll("("); | |
| 491 | ||
| 492 | try writer.writeAll("{"); | |
| 493 | ||
| 494 | for (keys, values, 0..) |key, value, i| { | |
| 495 | if (i != 0) | |
| 496 | try writer.writeAll(","); | |
| 497 | ||
| 498 | switch (key.decode()) { | |
| 499 | .static => |str| { | |
| 500 | // TODO: handle strings that cannot be printed as identifiers. | |
| 501 | try writer.writeAll(str.toSlice(&link.comp.intern_pool)); | |
| 502 | }, | |
| 503 | .computed => @panic("TODO: computed property"), | |
| 504 | } | |
| 505 | ||
| 506 | try writer.writeAll(":"); | |
| 507 | ||
| 508 | try link.printExpr(tree, value, writer); | |
| 509 | } | |
| 510 | ||
| 511 | try writer.writeAll("}"); | |
| 512 | ||
| 513 | if (level == .statement) | |
| 514 | try writer.writeAll(")"); | |
| 515 | }, | |
| 516 | } | |
| 517 | } | |
| 518 | ||
| 519 | pub fn canPrintWithoutEscape(c: u21) bool { | |
| 520 | const first_ascii_printable = 0x20; | |
| 521 | const last_ascii_printable = 0x7E; | |
| 522 | const first_high_surrogate = 0xD800; | |
| 523 | const last_low_surrogate = 0xDFFF; | |
| 524 | ||
| 525 | if (c <= last_ascii_printable) { | |
| 526 | return c >= first_ascii_printable and c != '\\' and c != '"'; | |
| 527 | } else { | |
| 528 | return c != 0xFEFF and (c < first_high_surrogate or c > last_low_surrogate); | |
| 529 | } | |
| 530 | } | |
| 531 | ||
| 532 | pub fn indexOfNeedsEscape(slice: []const u8) ?usize { | |
| 533 | var it = std.unicode.Wtf8Iterator{ .bytes = slice, .i = 0 }; | |
| 534 | var i: usize = 0; | |
| 535 | while (it.nextCodepoint()) |c| { | |
| 536 | if (!canPrintWithoutEscape(c)) | |
| 537 | return i; | |
| 538 | i = it.i; | |
| 539 | } | |
| 540 | return null; | |
| 541 | } | |
| 542 | ||
| 543 | // This function is loosely based on Bun's `writeJSONString`, which | |
| 544 | // is a Zig port of esbuild's `printUnquotedUTF16` function. | |
| 545 | // Both projects are licensed under MIT | |
| 546 | fn writeString(decoded: []const u8, writer: Writer) !void { | |
| 547 | try writer.writeByte('"'); | |
| 548 | ||
| 549 | var text = decoded; | |
| 550 | while (text.len > 0) { | |
| 551 | const width = std.unicode.utf8ByteSequenceLength(text[0]) catch | |
| 552 | unreachable; // string is encoded incorrectly | |
| 553 | ||
| 554 | const c: u21 = std.unicode.wtf8Decode(text[0..width]) catch | |
| 555 | unreachable; // string is encoded incorrectly | |
| 556 | ||
| 557 | if (canPrintWithoutEscape(c)) { | |
| 558 | if (indexOfNeedsEscape(text)) |j| { | |
| 559 | try writer.writeAll(text[0..j]); | |
| 560 | text = text[j..]; | |
| 561 | continue; | |
| 562 | } else { | |
| 563 | try writer.writeAll(text); | |
| 564 | break; | |
| 565 | } | |
| 566 | } | |
| 567 | ||
| 568 | text = text[width..]; | |
| 569 | ||
| 570 | switch (c) { | |
| 571 | // Special-case the bell character since it may cause dumping this file to | |
| 572 | // the terminal to make a sound, which is undesirable. Note that we can't | |
| 573 | // use an octal literal to print this shorter since octal literals are not | |
| 574 | // allowed in strict mode (or in template strings). | |
| 575 | 0x07 => { | |
| 576 | try writer.writeAll("\\x07"); | |
| 577 | }, | |
| 578 | 0x08 => { | |
| 579 | try writer.writeAll("\\b"); | |
| 580 | }, | |
| 581 | 0x0C => { | |
| 582 | try writer.writeAll("\\f"); | |
| 583 | }, | |
| 584 | '\n' => { | |
| 585 | try writer.writeAll("\\n"); | |
| 586 | }, | |
| 587 | std.ascii.control_code.cr => { | |
| 588 | try writer.writeAll("\\r"); | |
| 589 | }, | |
| 590 | std.ascii.control_code.vt => { | |
| 591 | try writer.writeAll("\\v"); | |
| 592 | }, | |
| 593 | '\\' => { | |
| 594 | try writer.writeAll("\\\\"); | |
| 595 | }, | |
| 596 | '"' => { | |
| 597 | try writer.writeAll("\\\""); | |
| 598 | }, | |
| 599 | '\t' => { | |
| 600 | try writer.writeAll("\\t"); | |
| 601 | }, | |
| 602 | ||
| 603 | else => { | |
| 604 | const hex_chars = "0123456789ABCDEF"; | |
| 605 | ||
| 606 | if (c < 0xFFFF) { | |
| 607 | const k = @as(usize, @intCast(c)); | |
| 608 | ||
| 609 | try writer.writeAll(&[_]u8{ | |
| 610 | '\\', | |
| 611 | 'u', | |
| 612 | hex_chars[(k >> 12) & 0xF], | |
| 613 | hex_chars[(k >> 8) & 0xF], | |
| 614 | hex_chars[(k >> 4) & 0xF], | |
| 615 | hex_chars[k & 0xF], | |
| 616 | }); | |
| 617 | } else { | |
| 618 | const first_high_surrogate = 0xD800; | |
| 619 | const first_low_surrogate = 0xDC00; | |
| 620 | ||
| 621 | const k = c - 0x10000; | |
| 622 | const lo = @as(usize, @intCast(first_high_surrogate + ((k >> 10) & 0x3FF))); | |
| 623 | const hi = @as(usize, @intCast(first_low_surrogate + (k & 0x3FF))); | |
| 624 | ||
| 625 | try writer.writeAll(&[_]u8{ | |
| 626 | '\\', | |
| 627 | 'u', | |
| 628 | hex_chars[lo >> 12], | |
| 629 | hex_chars[(lo >> 8) & 15], | |
| 630 | hex_chars[(lo >> 4) & 15], | |
| 631 | hex_chars[lo & 15], | |
| 632 | '\\', | |
| 633 | 'u', | |
| 634 | hex_chars[hi >> 12], | |
| 635 | hex_chars[(hi >> 8) & 15], | |
| 636 | hex_chars[(hi >> 4) & 15], | |
| 637 | hex_chars[hi & 15], | |
| 638 | }); | |
| 639 | } | |
| 640 | }, | |
| 641 | } | |
| 642 | } | |
| 643 | try writer.writeByte('"'); | |
| 644 | } | |
| 645 | ||
| 646 | fn printLocal(link: *Link, local: Tree.LocalIndex, writer: Writer) !void { | |
| 647 | var t = tracer.trace(@src()); | |
| 648 | defer t.end(); | |
| 649 | ||
| 650 | _ = link; // todo: be smarter about this | |
| 651 | try writer.print("v{d}", .{local}); | |
| 652 | } | |
| 653 | ||
| 654 | fn printLabelIdentifier(link: *Link, local: Tree.LocalIndex, writer: Writer) !void { | |
| 655 | var t = tracer.trace(@src()); | |
| 656 | defer t.end(); | |
| 657 | ||
| 658 | _ = link; // todo: be smarter about this | |
| 659 | try writer.print("label{d}", .{local}); | |
| 660 | } | |
| 661 | ||
| 662 | const GlobalOwner = enum { | |
| 663 | external, | |
| 664 | decl, | |
| 665 | }; | |
| 666 | ||
| 667 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence | |
| 668 | const Level = enum { | |
| 669 | /// Extra level used for object literals in arrow function returns | |
| 670 | statement, | |
| 671 | comma, | |
| 672 | assignment, | |
| 673 | conditional, | |
| 674 | logical_or, | |
| 675 | logical_and, | |
| 676 | bitwise_or, | |
| 677 | bitwise_xor, | |
| 678 | bitwise_and, | |
| 679 | equality, | |
| 680 | relational, | |
| 681 | bitshift, | |
| 682 | additive, | |
| 683 | multiplicative, | |
| 684 | exponentiation, | |
| 685 | prefix, | |
| 686 | postfix, | |
| 687 | new, | |
| 688 | access_and_call, | |
| 689 | member, | |
| 690 | ||
| 691 | pub fn needsParens(inner_level: Level, outer_level: Level) bool { | |
| 692 | return @intFromEnum(outer_level) > @intFromEnum(inner_level); | |
| 693 | } | |
| 694 | }; | |
| 695 | ||
| 696 | const Operator = enum { | |
| 697 | assign, | |
| 698 | assign_add, | |
| 699 | assign_sub, | |
| 700 | assign_mul, | |
| 701 | assign_div, | |
| 702 | add, | |
| 703 | sub, | |
| 704 | mul, | |
| 705 | div, | |
| 706 | eq, | |
| 707 | lt, | |
| 708 | gt, | |
| 709 | negate, | |
| 710 | bool_not, | |
| 711 | pre_inc, | |
| 712 | pre_dec, | |
| 713 | ||
| 714 | pub fn level(op: Operator) Level { | |
| 715 | return switch (op) { | |
| 716 | .assign, | |
| 717 | .assign_add, | |
| 718 | .assign_sub, | |
| 719 | .assign_mul, | |
| 720 | .assign_div, | |
| 721 | => .assignment, | |
| 722 | .add, .sub => .additive, | |
| 723 | .mul, .div => .multiplicative, | |
| 724 | .eq => .equality, | |
| 725 | .lt, .gt => .relational, | |
| 726 | ||
| 727 | .pre_inc, | |
| 728 | .pre_dec, | |
| 729 | .bool_not, | |
| 730 | .negate, | |
| 731 | => .prefix, | |
| 732 | }; | |
| 733 | } | |
| 734 | ||
| 735 | pub fn symbol(op: Operator) []const u8 { | |
| 736 | return switch (op) { | |
| 737 | .assign => "=", | |
| 738 | .assign_add => "+=", | |
| 739 | .assign_sub => "-=", | |
| 740 | .assign_mul => "*=", | |
| 741 | .assign_div => "/=", | |
| 742 | .add => "+", | |
| 743 | .sub => "-", | |
| 744 | .mul => "*", | |
| 745 | .div => "/", | |
| 746 | .eq => "===", | |
| 747 | .lt => "<", | |
| 748 | .gt => ">", | |
| 749 | ||
| 750 | .pre_inc => "++", | |
| 751 | .pre_dec => "++", | |
| 752 | ||
| 753 | .negate => "-", | |
| 754 | .bool_not => "!", | |
| 755 | }; | |
| 756 | } | |
| 757 | }; | |
| 758 | ||
| 759 | const LinkString = struct { | |
| 760 | start: u32, | |
| 761 | len: u32, | |
| 762 | ||
| 763 | fn slice(str: LinkString, link: *const Link) []const u8 { | |
| 764 | return link.string_bytes.items[str.start..][0..str.len]; | |
| 765 | } | |
| 766 | ||
| 767 | const Index = enum(u32) { | |
| 768 | _, | |
| 769 | ||
| 770 | pub fn toString(index: Index, link: *const Link) LinkString { | |
| 771 | return link.link_strings.keys()[@intFromEnum(index)]; | |
| 772 | } | |
| 773 | }; | |
| 774 | ||
| 775 | const Context = struct { | |
| 776 | link: *const Link, | |
| 777 | ||
| 778 | pub fn hash(ctx: Context, str: LinkString) u32 { | |
| 779 | assert(str.start + str.len < ctx.link.string_bytes.items.len); | |
| 780 | return @truncate(Wyhash.hash(0, str.slice(ctx.link))); | |
| 781 | } | |
| 782 | ||
| 783 | pub fn eql(ctx: Context, a: LinkString, b: LinkString, _: usize) bool { | |
| 784 | assert(a.start + a.len < ctx.link.string_bytes.items.len); | |
| 785 | assert(b.start + b.len < ctx.link.string_bytes.items.len); | |
| 786 | return a.start == b.start; | |
| 787 | } | |
| 788 | }; | |
| 789 | ||
| 790 | const SliceContext = struct { | |
| 791 | link: *const Link, | |
| 792 | ||
| 793 | pub fn hash(_: SliceContext, str: []const u8) u32 { | |
| 794 | return @truncate(Wyhash.hash(0, str)); | |
| 795 | } | |
| 796 | ||
| 797 | pub fn eql(ctx: SliceContext, a: []const u8, b: LinkString, _: usize) bool { | |
| 798 | // since the given string is not in string_bytes we do comparisons | |
| 799 | return std.mem.eql(u8, a, b.slice(ctx.link)); | |
| 800 | } | |
| 801 | }; | |
| 802 | }; | |
| 803 | ||
| 804 | const Link = @This(); | |
| 805 | const std = @import("std"); | |
| 806 | const assert = std.debug.assert; | |
| 807 | const Wyhash = std.hash.Wyhash; | |
| 808 | const log = std.log.scoped(.js_link); | |
| 809 | const Writer = std.io.AnyWriter; | |
| 810 | ||
| 811 | const Comp = @import("../Comp.zig"); | |
| 812 | const Decl = Comp.Decl; | |
| 813 | ||
| 814 | const Js = @import("Js.zig"); | |
| 815 | const Tree = @import("Tree.zig"); | |
| 816 | const Node = Tree.Node; | |
| 817 | ||
| 818 | const InternPool = @import("../InternPool.zig"); | |
| 819 | const String = InternPool.String; | |
| 820 | ||
| 821 | const tracer = @import("tracer"); |
src/core/js/NodeGen.zig created+751| ... | ... | @@ -0,0 +1,751 @@ |
| 1 | //! Air -> Js.Tree | |
| 2 | gpa: Allocator, | |
| 3 | arena: Allocator, | |
| 4 | air: *const Air, | |
| 5 | ip: *const InternPool, | |
| 6 | ||
| 7 | nodes: Node.List, | |
| 8 | extra: dod.ExtraStore(.js_tree, .@"var"), | |
| 9 | extern_symbols: std.AutoArrayHashMapUnmanaged(InternPool.String.WithLen, void), | |
| 10 | top_level_value_deps: std.AutoArrayHashMapUnmanaged(Value, void), | |
| 11 | ||
| 12 | param_count: u32, | |
| 13 | air_node_map: []MapEntry, | |
| 14 | scratch: std.ArrayListUnmanaged(u32), | |
| 15 | next_id: u32, | |
| 16 | next_label: u32, | |
| 17 | ||
| 18 | const Error = error{ | |
| 19 | OutOfMemory, | |
| 20 | }; | |
| 21 | ||
| 22 | const MapEntry = union { | |
| 23 | none: void, | |
| 24 | node: Node.Index, | |
| 25 | block: packed struct { | |
| 26 | used: bool = false, | |
| 27 | index: u31, | |
| 28 | }, | |
| 29 | }; | |
| 30 | ||
| 31 | pub fn init(gpa: Allocator, arena: Allocator, ip: *const InternPool, air: *const Air) !NodeGen { | |
| 32 | const t = tracer.trace(@src()); | |
| 33 | defer t.end(); | |
| 34 | ||
| 35 | const air_node_map = try arena.alloc(MapEntry, air.insts.len); | |
| 36 | if (std.debug.runtime_safety) { | |
| 37 | @memset(air_node_map, .{ .none = {} }); | |
| 38 | } | |
| 39 | const param_count = air.paramCount(); | |
| 40 | return .{ | |
| 41 | .gpa = gpa, | |
| 42 | .arena = arena, | |
| 43 | .air = air, | |
| 44 | .ip = ip, | |
| 45 | .param_count = param_count, | |
| 46 | .nodes = .{}, | |
| 47 | .extern_symbols = .{}, | |
| 48 | .top_level_value_deps = .{}, | |
| 49 | .air_node_map = air_node_map, | |
| 50 | .extra = .{}, | |
| 51 | .scratch = .{}, | |
| 52 | .next_id = param_count, | |
| 53 | .next_label = 0, | |
| 54 | }; | |
| 55 | } | |
| 56 | ||
| 57 | pub fn deinit(c: *NodeGen) void { | |
| 58 | const t = tracer.trace(@src()); | |
| 59 | defer t.end(); | |
| 60 | ||
| 61 | defer c.* = undefined; | |
| 62 | c.nodes.deinit(c.gpa); | |
| 63 | c.extra.deinit(c.gpa); | |
| 64 | c.extern_symbols.deinit(c.gpa); | |
| 65 | c.top_level_value_deps.deinit(c.gpa); | |
| 66 | ||
| 67 | // arena can deinit the rest | |
| 68 | } | |
| 69 | ||
| 70 | pub fn finalize(c: *NodeGen) !Tree { | |
| 71 | const t = tracer.trace(@src()); | |
| 72 | defer t.end(); | |
| 73 | ||
| 74 | // todo: errdefer will probably leave memory in a fully invalid state | |
| 75 | try c.nodes.setCapacity(c.gpa, c.nodes.len); | |
| 76 | ||
| 77 | var extra = try c.extra.toOwnedStore(c.gpa); | |
| 78 | errdefer extra.deinit(c.gpa); | |
| 79 | ||
| 80 | // todo: i dont like this dupe | |
| 81 | const extern_symbols = try c.gpa.dupe(InternPool.String.WithLen, c.extern_symbols.keys()); | |
| 82 | errdefer c.gpa.free(extern_symbols); | |
| 83 | ||
| 84 | const top_level_value_deps = try c.gpa.dupe(Value, c.top_level_value_deps.keys()); | |
| 85 | errdefer c.gpa.free(top_level_value_deps); | |
| 86 | ||
| 87 | defer c.deinit(); | |
| 88 | ||
| 89 | return .{ | |
| 90 | .param_count = c.param_count, | |
| 91 | .nodes = c.nodes.toOwnedSlice(), | |
| 92 | .extern_symbols = extern_symbols, | |
| 93 | .top_level_value_deps = top_level_value_deps, | |
| 94 | .extra = extra, | |
| 95 | }; | |
| 96 | } | |
| 97 | ||
| 98 | pub fn build(c: *NodeGen) !void { | |
| 99 | const t = tracer.trace(@src()); | |
| 100 | defer t.end(); | |
| 101 | ||
| 102 | assert(c.air.instTag(.main_block) == .block); | |
| 103 | const statements = c.air.instData(.block, .main_block).slice(c.air); | |
| 104 | ||
| 105 | const scratch_top = c.scratch.items.len; | |
| 106 | defer c.scratch.shrinkRetainingCapacity(scratch_top); | |
| 107 | ||
| 108 | _ = try c.add(.root, undefined); | |
| 109 | ||
| 110 | if (statements.len > 0) { | |
| 111 | for (statements[0..c.param_count], 1..) |stmt, expected_id| { | |
| 112 | assert(@intFromEnum(stmt) == expected_id); | |
| 113 | assert(c.air.instTag(stmt) == .arg); | |
| 114 | c.air_node_map[@intFromEnum(stmt)] = .{ | |
| 115 | .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }), | |
| 116 | }; | |
| 117 | } | |
| 118 | ||
| 119 | for (statements[c.param_count..]) |stmt| { | |
| 120 | try c.codeGenInst(stmt); | |
| 121 | } | |
| 122 | } | |
| 123 | ||
| 124 | const items = c.scratch.items[scratch_top..]; | |
| 125 | const top = c.extra.list.items.len; | |
| 126 | try c.extra.list.appendSlice(c.gpa, items); | |
| 127 | c.nodes.items(.data)[0] = EncodedData.encode(.root, .{ | |
| 128 | .start = @intCast(top), | |
| 129 | .len = @intCast(items.len), | |
| 130 | }); | |
| 131 | } | |
| 132 | ||
| 133 | fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { | |
| 134 | const t = tracer.traceFmt(@src(), "#{d} {s}", .{ @intFromEnum(inst), @tagName(c.air.instTag(inst)) }); | |
| 135 | defer t.end(); | |
| 136 | ||
| 137 | switch (c.air.instTag(inst)) { | |
| 138 | .arg => unreachable, // codegen must skip over these instructions. | |
| 139 | ||
| 140 | .block => try c.codeGenBlock(inst), | |
| 141 | ||
| 142 | .js_global_ref => { | |
| 143 | const data: InternPool.String.WithLen = c.air.instData(.js_global_ref, inst); | |
| 144 | try c.extern_symbols.put(c.gpa, data, {}); | |
| 145 | c.air_node_map[@intFromEnum(inst)] = .{ | |
| 146 | .node = try c.add(.global, .{ .name = data }), | |
| 147 | }; | |
| 148 | }, | |
| 149 | ||
| 150 | .memory => { | |
| 151 | const i = @intFromEnum(inst); | |
| 152 | const data: Air.Memory = c.air.instData(.memory, inst); | |
| 153 | const value: Tree.Node.Index.Optional = if (data.init.isVoid()) | |
| 154 | .none | |
| 155 | else | |
| 156 | (try c.fetchExpression(data.init)).toOptional(); | |
| 157 | ||
| 158 | try c.addStmt(try c.add(.stmt_declare_local, .{ | |
| 159 | .identifier_index = c.next_id, | |
| 160 | .init = value, | |
| 161 | })); | |
| 162 | const id = try c.add(.local, .{ .id = c.next_id }); | |
| 163 | c.next_id += 1; | |
| 164 | c.air_node_map[i] = .{ .node = id }; | |
| 165 | }, | |
| 166 | ||
| 167 | inline .gt, | |
| 168 | .lt, | |
| 169 | .eq, | |
| 170 | .add, | |
| 171 | .sub, | |
| 172 | .mul, | |
| 173 | .div, | |
| 174 | => |tag| { | |
| 175 | const data: Air.Bin = c.air.instData(tag, inst); | |
| 176 | const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{ | |
| 177 | .lhs = try c.fetchExpression(data.lhs), | |
| 178 | .rhs = try c.fetchExpression(data.rhs), | |
| 179 | }); | |
| 180 | try c.markExpression(inst, node); | |
| 181 | }, | |
| 182 | ||
| 183 | .store => { | |
| 184 | const data: Air.Store = c.air.instData(.store, inst); | |
| 185 | ||
| 186 | const target = try c.fetchInstExpression(data.memory); | |
| 187 | ||
| 188 | if (data.operand.kind == .index) { | |
| 189 | const index = data.operand.decode().index; | |
| 190 | switch (c.air.instTag(index)) { | |
| 191 | inline .add, | |
| 192 | .sub, | |
| 193 | .mul, | |
| 194 | .div, | |
| 195 | => |tag| { | |
| 196 | const inner_data: Air.Bin = c.air.instData(tag, index); | |
| 197 | if (inner_data.lhs.eql(data.memory.toRef())) { | |
| 198 | if ((tag == .add or tag == .sub) and | |
| 199 | inner_data.rhs.kind == .value) | |
| 200 | { | |
| 201 | // if (inner_data.rhs.decode().value.toFloat(c.ip)) |f| { | |
| 202 | // if (f == 1) { | |
| 203 | // // convert this into ++ or -- | |
| 204 | // const node = try c.add( | |
| 205 | // if (tag == .add) | |
| 206 | // .pre_inc | |
| 207 | // else | |
| 208 | // .pre_dec, | |
| 209 | // .{ | |
| 210 | // .operand = target, | |
| 211 | // }, | |
| 212 | // ); | |
| 213 | // try c.markExpression(inst, node); | |
| 214 | ||
| 215 | // return; | |
| 216 | // } | |
| 217 | // } | |
| 218 | } | |
| 219 | ||
| 220 | // convert into a compound assignment | |
| 221 | const node = try c.add( | |
| 222 | @field(Tree.Tag, "assign_" ++ @tagName(tag)), | |
| 223 | .{ | |
| 224 | .lhs = target, | |
| 225 | .rhs = try c.fetchExpression(inner_data.rhs), | |
| 226 | }, | |
| 227 | ); | |
| 228 | try c.markExpression(inst, node); | |
| 229 | ||
| 230 | return; | |
| 231 | } | |
| 232 | }, | |
| 233 | else => {}, | |
| 234 | } | |
| 235 | } | |
| 236 | ||
| 237 | const node = try c.add(.assign, .{ | |
| 238 | .lhs = target, | |
| 239 | .rhs = try c.fetchExpression(data.operand), | |
| 240 | }); | |
| 241 | try c.markExpression(inst, node); | |
| 242 | }, | |
| 243 | ||
| 244 | inline .negate, | |
| 245 | .bool_not, | |
| 246 | => |tag| { | |
| 247 | const data: Air.Un = c.air.instData(tag, inst); | |
| 248 | const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{ | |
| 249 | .operand = try c.fetchExpression(data.operand), | |
| 250 | }); | |
| 251 | try c.markExpression(inst, node); | |
| 252 | }, | |
| 253 | ||
| 254 | .field_opaque => { | |
| 255 | const data: Air.FieldOpaque = c.air.instData(.field_opaque, inst); | |
| 256 | const node = try c.add(.field, .{ | |
| 257 | .base = try c.fetchExpression(data.base), | |
| 258 | .field = data.field, | |
| 259 | }); | |
| 260 | try c.markExpression(inst, node); | |
| 261 | }, | |
| 262 | ||
| 263 | .field_struct => { | |
| 264 | const data: Air.FieldStruct = c.air.instData(.field_struct, inst); | |
| 265 | const node = try c.add(.field, .{ | |
| 266 | .base = try c.fetchInstExpression(data.base), | |
| 267 | .field = field_name: { | |
| 268 | break :field_name c.air.typeOfIndex(data.base, c.ip) | |
| 269 | .toStructType(c.ip).? | |
| 270 | .field_keys[data.field_index]; | |
| 271 | }, | |
| 272 | }); | |
| 273 | try c.markExpression(inst, node); | |
| 274 | }, | |
| 275 | ||
| 276 | inline .call, .new => |tag| { | |
| 277 | const data: Air.Call = c.air.instData(tag, inst); | |
| 278 | const call_data = data.call_data.get(c.air); | |
| 279 | ||
| 280 | try c.extra.list.ensureUnusedCapacity(c.gpa, call_data.args.len); | |
| 281 | const start = c.extra.list.items.len; | |
| 282 | c.extra.list.items.len += call_data.args.len; | |
| 283 | ||
| 284 | const items = c.extra.list.items[start..][0..call_data.args.len]; | |
| 285 | ||
| 286 | for (call_data.args.slice(c.air), items) |arg, *dest| { | |
| 287 | dest.* = @intFromEnum(try c.fetchExpression(arg)); | |
| 288 | } | |
| 289 | ||
| 290 | const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{ | |
| 291 | .base = try c.fetchExpression(data.base), | |
| 292 | .args = try c.extra.append(c.gpa, Tree.IndexList, .{ | |
| 293 | .start = @intCast(start), | |
| 294 | .len = call_data.args.len, | |
| 295 | }), | |
| 296 | }); | |
| 297 | try c.markExpression(inst, node); | |
| 298 | }, | |
| 299 | ||
| 300 | .@"if" => { | |
| 301 | const data: Air.If = c.air.instData(.@"if", inst); | |
| 302 | const extra = data.data.get(c.air); | |
| 303 | var cond = try c.fetchExpression(data.cond); | |
| 304 | ||
| 305 | if (c.canLowerToExpression(extra.yes) and | |
| 306 | c.canLowerToExpression(extra.no)) | |
| 307 | { | |
| 308 | const yes = try c.lowerToExpression(extra.yes); | |
| 309 | const no = try c.lowerToExpression(extra.yes); | |
| 310 | ||
| 311 | const ternary = try c.add(.ternary, .{ | |
| 312 | .cond = cond, | |
| 313 | .data = try c.extra.append(c.gpa, Tree.Ternary.Data, .{ | |
| 314 | .yes = yes, | |
| 315 | .no = no, | |
| 316 | }), | |
| 317 | }); | |
| 318 | ||
| 319 | try c.markExpression(inst, ternary); | |
| 320 | } else { | |
| 321 | const flags = c.air.instFlags(inst); | |
| 322 | const uses = flags.uses; | |
| 323 | ||
| 324 | const target = if (uses != .none) | |
| 325 | try c.addTemporary() | |
| 326 | else | |
| 327 | null; | |
| 328 | ||
| 329 | var yes = try c.codeGenBranch(extra.yes, target); | |
| 330 | var no = try c.codeGenBranch(extra.no, target); | |
| 331 | assert(!((yes == null) and (no == null))); // no "void else void" branches | |
| 332 | ||
| 333 | // when `yes` is void, swap the condition so that the `else` is omitted | |
| 334 | if (yes == null) { | |
| 335 | const temp_yes = yes; | |
| 336 | yes = no; | |
| 337 | no = temp_yes; | |
| 338 | cond = try c.add(.bool_not, .{ | |
| 339 | .operand = cond, | |
| 340 | }); | |
| 341 | } | |
| 342 | ||
| 343 | const main_expr = try c.add(.stmt_if, .{ | |
| 344 | .cond = cond, | |
| 345 | .data = try c.extra.append(c.gpa, Tree.StmtIf.Data, .{ | |
| 346 | .yes = yes.?.list, | |
| 347 | .no = if (no) |branch| | |
| 348 | branch.list | |
| 349 | else | |
| 350 | .{ .start = 0, .len = 0 }, | |
| 351 | .flags = .{ | |
| 352 | .block_label = @intCast(c.next_label + 1), | |
| 353 | .yes_label = yes.?.requires_label, | |
| 354 | .no_label = if (no) |branch| branch.requires_label else false, | |
| 355 | }, | |
| 356 | }), | |
| 357 | }); | |
| 358 | ||
| 359 | try c.addStmt(main_expr); | |
| 360 | ||
| 361 | if (target) |node| { | |
| 362 | try c.markExpression(inst, node); | |
| 363 | } | |
| 364 | } | |
| 365 | }, | |
| 366 | ||
| 367 | .@"break" => { | |
| 368 | const brk: Air.Break = c.air.instData(.@"break", inst); | |
| 369 | ||
| 370 | const operand = try c.fetchExpression(brk.value); | |
| 371 | ||
| 372 | // break #0 -> return | |
| 373 | if (brk.block_ref == .main_block) { | |
| 374 | try c.addStmt(try c.add(.stmt_return, .{ | |
| 375 | .operand = operand.toOptional(), | |
| 376 | })); | |
| 377 | } else { | |
| 378 | const block = &c.air_node_map[@intFromEnum(brk.block_ref)].block; | |
| 379 | ||
| 380 | const block_insts = c.air.instData(.block, brk.block_ref).slice(c.air); | |
| 381 | if (block_insts[block_insts.len - 1] != inst) { | |
| 382 | block.used = true; | |
| 383 | } | |
| 384 | ||
| 385 | const assignment: Node.Index.Optional = @enumFromInt(c.scratch.items[block.index]); | |
| 386 | if (assignment.unwrap()) |target| { | |
| 387 | try c.addStmt(try c.add(.assign, .{ | |
| 388 | .lhs = target, | |
| 389 | .rhs = operand, | |
| 390 | })); | |
| 391 | } | |
| 392 | ||
| 393 | try c.addStmt(try c.add(.stmt_break, .{ | |
| 394 | .label = c.scratch.items[block.index + 1], | |
| 395 | })); | |
| 396 | } | |
| 397 | }, | |
| 398 | ||
| 399 | .struct_init => { | |
| 400 | const struct_init: Air.StructInit = c.air.instData(.struct_init, inst); | |
| 401 | const struct_type = struct_init.ty.toStructType(c.ip).?; | |
| 402 | ||
| 403 | const property_count: u32 = @intCast(struct_type.field_types.len); | |
| 404 | const store_offset = try c.extra.alloc(c.gpa, property_count * 2); | |
| 405 | ||
| 406 | for ( | |
| 407 | struct_type.field_keys, | |
| 408 | struct_init.valuesPtr(c.air), | |
| 409 | store_offset.., | |
| 410 | store_offset + property_count.., | |
| 411 | ) |key, value, key_i, value_i| { | |
| 412 | const key_encoded = Tree.ObjectLiteral.Key.Decoded.encode(.{ .static = key }); | |
| 413 | c.extra.list.items[key_i] = @bitCast(key_encoded); | |
| 414 | const in = try c.fetchExpression(value); | |
| 415 | c.extra.list.items[value_i] = @intFromEnum(in); | |
| 416 | } | |
| 417 | ||
| 418 | const node = try c.add(.object_literal, .{ | |
| 419 | .extra_index = store_offset, | |
| 420 | .property_count = property_count, | |
| 421 | }); | |
| 422 | try c.markExpression(inst, node); | |
| 423 | }, | |
| 424 | ||
| 425 | // else => |tag| std.debug.panic("TODO JS codeGenInst for .{s}", .{@tagName(tag)}), | |
| 426 | } | |
| 427 | } | |
| 428 | ||
| 429 | fn markExpression(c: *NodeGen, inst: Air.Inst.Index, node: Node.Index) !void { | |
| 430 | const t = tracer.trace(@src()); | |
| 431 | defer t.end(); | |
| 432 | ||
| 433 | const flags: Air.Flags = c.air.instFlags(inst); | |
| 434 | const i = @intFromEnum(inst); | |
| 435 | switch (flags.uses) { | |
| 436 | .none => { | |
| 437 | try c.addStmt(node); | |
| 438 | }, | |
| 439 | .single => { | |
| 440 | // TODO: this is not aware of side effect fences | |
| 441 | c.air_node_map[i] = .{ .node = node }; | |
| 442 | }, | |
| 443 | .multi => { | |
| 444 | const decl = try c.add(.stmt_declare_local, .{ | |
| 445 | .identifier_index = c.next_id, | |
| 446 | .init = node.toOptional(), | |
| 447 | }); | |
| 448 | try c.addStmt(decl); | |
| 449 | const id = try c.add(.local, .{ .id = c.next_id }); | |
| 450 | c.next_id += 1; | |
| 451 | c.air_node_map[i] = .{ .node = id }; | |
| 452 | }, | |
| 453 | } | |
| 454 | } | |
| 455 | ||
| 456 | fn fetchExpression(c: *NodeGen, ref: Air.Inst.Ref) !Node.Index { | |
| 457 | const t = tracer.trace(@src()); | |
| 458 | defer t.end(); | |
| 459 | ||
| 460 | return switch (ref.decode()) { | |
| 461 | .value => |v| c.valueToExpression(v), | |
| 462 | .index => |index| fetchInstExpression(c, index), | |
| 463 | }; | |
| 464 | } | |
| 465 | ||
| 466 | fn fetchInstExpression(c: *NodeGen, index: Air.Inst.Index) !Node.Index { | |
| 467 | const t = tracer.trace(@src()); | |
| 468 | defer t.end(); | |
| 469 | ||
| 470 | const data = c.air_node_map[@intFromEnum(index)]; | |
| 471 | if (std.debug.runtime_safety) { | |
| 472 | const flags: Air.Flags = c.air.instFlags(index); | |
| 473 | switch (flags.uses) { | |
| 474 | .none => if (std.debug.runtime_safety) { | |
| 475 | // incorrectly marked instruction as unused | |
| 476 | std.debug.panic("called fetchInstExpression(#{d}), but no expression was generated", .{ | |
| 477 | @intFromEnum(index), | |
| 478 | }); | |
| 479 | } else unreachable, | |
| 480 | .single => {}, | |
| 481 | .multi => assert(c.nodes.items(.tag)[@intFromEnum(data.node)] == .local), | |
| 482 | } | |
| 483 | } | |
| 484 | return data.node; | |
| 485 | } | |
| 486 | ||
| 487 | fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { | |
| 488 | const t = tracer.trace(@src()); | |
| 489 | defer t.end(); | |
| 490 | ||
| 491 | const intern_tag = v.valueTag(c.ip); | |
| 492 | switch (intern_tag) { | |
| 493 | .removed => unreachable, // UAF | |
| 494 | ||
| 495 | .function_type, | |
| 496 | .simple_type, | |
| 497 | .struct_type, | |
| 498 | => unreachable, // Type values are not allowed here | |
| 499 | ||
| 500 | .int32, | |
| 501 | .float, | |
| 502 | .comptime_float, | |
| 503 | .string, | |
| 504 | .simple_value, | |
| 505 | .struct_value, | |
| 506 | => return try c.add(.plain_value, .{ .value = v }), | |
| 507 | ||
| 508 | // Hoist functions as top level values | |
| 509 | // TODO: Consider hoisting large values too | |
| 510 | .func => { | |
| 511 | try c.top_level_value_deps.put(c.gpa, v, {}); | |
| 512 | return try c.add(.top_level_value, .{ .value = v }); | |
| 513 | }, | |
| 514 | } | |
| 515 | } | |
| 516 | ||
| 517 | fn addTemporary(c: *NodeGen) !Node.Index { | |
| 518 | const t = tracer.trace(@src()); | |
| 519 | defer t.end(); | |
| 520 | ||
| 521 | try c.addStmt(try c.add(.stmt_declare_local, .{ | |
| 522 | .identifier_index = c.next_id, | |
| 523 | .init = .none, | |
| 524 | })); | |
| 525 | const id = try c.add(.local, .{ .id = c.next_id }); | |
| 526 | c.next_id += 1; | |
| 527 | return id; | |
| 528 | } | |
| 529 | ||
| 530 | const Branch = struct { | |
| 531 | requires_label: bool, | |
| 532 | list: Tree.IndexList, | |
| 533 | }; | |
| 534 | ||
| 535 | fn codeGenBranch(c: *NodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Branch { | |
| 536 | const t = tracer.trace(@src()); | |
| 537 | defer t.end(); | |
| 538 | ||
| 539 | const scratch_top = c.scratch.items.len; | |
| 540 | defer c.scratch.shrinkRetainingCapacity(scratch_top); | |
| 541 | ||
| 542 | c.next_label += 1; | |
| 543 | defer c.next_label -= 1; | |
| 544 | const this_label = c.next_label; | |
| 545 | ||
| 546 | var requires_label = false; | |
| 547 | var items = switch (inst.decode()) { | |
| 548 | .value => |v| items: { | |
| 549 | if (v == .void) | |
| 550 | return null; | |
| 551 | if (temporary) |temp| { | |
| 552 | const node = try c.valueToExpression(v); | |
| 553 | try c.addStmt(try c.add(.assign, .{ | |
| 554 | .lhs = temp, | |
| 555 | .rhs = node, | |
| 556 | })); | |
| 557 | } | |
| 558 | break :items c.scratch.items[scratch_top..]; | |
| 559 | }, | |
| 560 | .index => |i| items: { | |
| 561 | assert(c.air.instTag(i) == .block); | |
| 562 | c.air_node_map[@intFromEnum(i)] = .{ | |
| 563 | .block = .{ .index = @intCast(scratch_top) }, | |
| 564 | }; | |
| 565 | ||
| 566 | try c.scratch.appendSlice(c.arena, &.{ | |
| 567 | @intFromEnum(if (temporary) |temp| temp.toOptional() else .none), | |
| 568 | this_label, | |
| 569 | }); | |
| 570 | ||
| 571 | const block: Air.IndexList = c.air.instData(.block, i); | |
| 572 | for (block.slice(c.air)) |item| { | |
| 573 | try c.codeGenInst(item); | |
| 574 | } | |
| 575 | ||
| 576 | requires_label = c.air_node_map[@intFromEnum(i)].block.used; | |
| 577 | ||
| 578 | break :items c.scratch.items[scratch_top + 2 ..]; | |
| 579 | }, | |
| 580 | }; | |
| 581 | ||
| 582 | const last_node = c.scratch.items[c.scratch.items.len - 1]; | |
| 583 | if (c.nodes.items(.tag)[last_node] == .stmt_break and | |
| 584 | c.nodes.items(.data)[last_node].decode(.stmt_break, .stmt_break).label == this_label) | |
| 585 | { | |
| 586 | items.len -= 1; | |
| 587 | c.nodes.len -= 1; | |
| 588 | } | |
| 589 | ||
| 590 | const top = c.extra.list.items.len; | |
| 591 | try c.extra.list.appendSlice(c.gpa, items); | |
| 592 | return .{ | |
| 593 | .requires_label = requires_label, | |
| 594 | .list = .{ | |
| 595 | .start = @intCast(top), | |
| 596 | .len = @intCast(items.len), | |
| 597 | }, | |
| 598 | }; | |
| 599 | } | |
| 600 | ||
| 601 | fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void { | |
| 602 | const t = tracer.trace(@src()); | |
| 603 | defer t.end(); | |
| 604 | ||
| 605 | const scratch_top = c.scratch.items.len; | |
| 606 | errdefer c.scratch.shrinkRetainingCapacity(scratch_top); | |
| 607 | ||
| 608 | c.next_label += 1; | |
| 609 | defer c.next_label -= 1; | |
| 610 | ||
| 611 | const next_label = c.next_label; | |
| 612 | ||
| 613 | try c.scratch.appendSlice(c.arena, &.{ | |
| 614 | // @intFromEnum(if (temporary) |temp| temp.toOptional() else .none), | |
| 615 | @intFromEnum(Node.Index.Optional.none), | |
| 616 | next_label, | |
| 617 | }); | |
| 618 | ||
| 619 | c.air_node_map[@intFromEnum(inst)] = .{ | |
| 620 | .block = .{ .index = @intCast(scratch_top) }, | |
| 621 | }; | |
| 622 | const block: Air.IndexList = c.air.instData(.block, inst); | |
| 623 | ||
| 624 | for (block.slice(c.air)) |item| { | |
| 625 | try c.codeGenInst(item); | |
| 626 | } | |
| 627 | ||
| 628 | const items = c.scratch.items[scratch_top + 2 ..]; | |
| 629 | const top = c.extra.list.items.len; | |
| 630 | try c.extra.list.appendSlice(c.gpa, items); | |
| 631 | c.scratch.shrinkRetainingCapacity(scratch_top); | |
| 632 | try c.addStmt(try c.add(.stmt_block, .{ | |
| 633 | .label = c.next_label, | |
| 634 | .content = try c.extra.append(c.gpa, Tree.IndexList, .{ | |
| 635 | .start = @intCast(top), | |
| 636 | .len = @intCast(items.len), | |
| 637 | }), | |
| 638 | })); | |
| 639 | } | |
| 640 | ||
| 641 | // Ideally, we convert everything to an expression. This allows for much | |
| 642 | // more compact code. When that isnt possible (`return`, `while`, etc), | |
| 643 | // we detect that here and ensure to lower `if` to actual if statements, | |
| 644 | // and so on. | |
| 645 | fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { | |
| 646 | const t = tracer.trace(@src()); | |
| 647 | defer t.end(); | |
| 648 | ||
| 649 | return switch (inst.decode()) { | |
| 650 | .value => true, | |
| 651 | .index => |index| switch (c.air.instTag(index)) { | |
| 652 | .js_global_ref, | |
| 653 | .call, | |
| 654 | .new, | |
| 655 | .arg, | |
| 656 | .field_opaque, | |
| 657 | .field_struct, | |
| 658 | .add, | |
| 659 | .sub, | |
| 660 | .mul, | |
| 661 | .div, | |
| 662 | .eq, | |
| 663 | .lt, | |
| 664 | .gt, | |
| 665 | .negate, | |
| 666 | .bool_not, | |
| 667 | .store, | |
| 668 | .struct_init, | |
| 669 | => true, | |
| 670 | ||
| 671 | // JavaScript variable declarations require statements. | |
| 672 | // Later, this could be made true if we can hoist temporaries up. | |
| 673 | .memory => false, | |
| 674 | ||
| 675 | // break/return must be a js statement | |
| 676 | .@"break", | |
| 677 | => false, | |
| 678 | ||
| 679 | .block => { | |
| 680 | // TODO: blocks that are | |
| 681 | // - all statements except last are lowerable | |
| 682 | // - the last statement is lowerable OR a break of this block | |
| 683 | return false; | |
| 684 | }, | |
| 685 | ||
| 686 | .@"if" => { | |
| 687 | const call = c.air.instData(.@"if", index); | |
| 688 | const data = call.data.get(c.air); | |
| 689 | ||
| 690 | return c.canLowerToExpression(call.cond) and | |
| 691 | c.canLowerToExpression(data.yes) and | |
| 692 | c.canLowerToExpression(data.no); | |
| 693 | }, | |
| 694 | }, | |
| 695 | }; | |
| 696 | } | |
| 697 | ||
| 698 | fn lowerToExpression(c: *NodeGen, ref: Air.Inst.Ref) !Node.Index { | |
| 699 | const t = tracer.trace(@src()); | |
| 700 | defer t.end(); | |
| 701 | ||
| 702 | assert(c.canLowerToExpression(ref)); | |
| 703 | return switch (ref.decode()) { | |
| 704 | .value => |v| try c.valueToExpression(v), | |
| 705 | .index => |index| { | |
| 706 | try c.codeGenInst(index); | |
| 707 | return c.fetchExpression(ref); | |
| 708 | }, | |
| 709 | }; | |
| 710 | } | |
| 711 | ||
| 712 | fn add(c: *NodeGen, comptime tag: Tree.Tag, data: tag.Data()) !Node.Index { | |
| 713 | const t = tracer.trace(@src()); | |
| 714 | defer t.end(); | |
| 715 | ||
| 716 | try c.nodes.append(c.gpa, .{ | |
| 717 | .tag = tag, | |
| 718 | .data = EncodedData.encode(tag, data), | |
| 719 | }); | |
| 720 | return @enumFromInt(c.nodes.len - 1); | |
| 721 | } | |
| 722 | ||
| 723 | fn addStmt(c: *NodeGen, stmt: Node.Index) !void { | |
| 724 | const t = tracer.trace(@src()); | |
| 725 | defer t.end(); | |
| 726 | ||
| 727 | try c.scratch.append(c.arena, @intFromEnum(stmt)); | |
| 728 | } | |
| 729 | ||
| 730 | const NodeGen = @This(); | |
| 731 | ||
| 732 | const std = @import("std"); | |
| 733 | const assert = std.debug.assert; | |
| 734 | const Allocator = std.mem.Allocator; | |
| 735 | ||
| 736 | const InternPool = @import("../InternPool.zig"); | |
| 737 | const Value = InternPool.Value; | |
| 738 | const NullTerminatedString = InternPool.NullTerminatedString; | |
| 739 | ||
| 740 | const Comp = @import("../Comp.zig"); | |
| 741 | const Decl = Comp.Decl; | |
| 742 | ||
| 743 | const Tree = @import("Tree.zig"); | |
| 744 | const EncodedData = Tree.EncodedData; | |
| 745 | const Node = Tree.Node; | |
| 746 | const LocalIndex = Tree.LocalIndex; | |
| 747 | ||
| 748 | const Air = @import("../Air.zig"); | |
| 749 | ||
| 750 | const dod = @import("../array.zig"); | |
| 751 | const tracer = @import("tracer"); |
src/core/js/Tree.zig created+300| ... | ... | @@ -0,0 +1,300 @@ |
| 1 | //! JavaScript Function Representation is the last representation before javascript | |
| 2 | //! source bytes are created. One notable thing here is that all local identifiers | |
| 3 | //! are numbered, instead of being named. The first `param_count` local identifiers | |
| 4 | //! refer to parameters, and the rest refer to the respective block that defined them. | |
| 5 | ||
| 6 | param_count: u32, | |
| 7 | nodes: Node.List.Slice, | |
| 8 | extern_symbols: []String.WithLen, | |
| 9 | top_level_value_deps: []Value, | |
| 10 | extra: ExtraStore, | |
| 11 | ||
| 12 | pub const LocalIndex = u32; | |
| 13 | pub const ExtraStore = extra.ExtraStore(.js_tree, .@"const"); | |
| 14 | pub const EncodedData = extra.EncodedData(Tag, u64); | |
| 15 | pub const ExtraIndex = ExtraStore.Index; | |
| 16 | ||
| 17 | pub const Tag = enum { | |
| 18 | root, | |
| 19 | /// Only allow simple value types: int, float, string, bool | |
| 20 | /// This value type is inlined into the call site | |
| 21 | plain_value, | |
| 22 | /// Reference to a top level value. (give a Value) | |
| 23 | /// This is used for function values / large constant objects | |
| 24 | top_level_value, | |
| 25 | /// Reference to a local identifier (give an VarId) | |
| 26 | local, | |
| 27 | /// Reference to a global identifier (give a NullTerminatedString) | |
| 28 | global, | |
| 29 | // Bin ops | |
| 30 | assign, | |
| 31 | assign_add, | |
| 32 | assign_sub, | |
| 33 | assign_mul, | |
| 34 | assign_div, | |
| 35 | add, | |
| 36 | sub, | |
| 37 | mul, | |
| 38 | div, | |
| 39 | eq, | |
| 40 | lt, | |
| 41 | gt, | |
| 42 | // Exprs | |
| 43 | call, | |
| 44 | new, | |
| 45 | field, | |
| 46 | ternary, | |
| 47 | object_literal, | |
| 48 | // Prefix ops | |
| 49 | negate, | |
| 50 | bool_not, | |
| 51 | pre_inc, | |
| 52 | pre_dec, | |
| 53 | // Statements | |
| 54 | /// `let ... = ...` | |
| 55 | stmt_declare_local, | |
| 56 | /// `if (...) ... else ...` | |
| 57 | stmt_if, | |
| 58 | /// `break ... ...` | |
| 59 | stmt_break, | |
| 60 | /// `return ...` (break from block #0) | |
| 61 | stmt_return, | |
| 62 | /// `...: { ... }` | |
| 63 | stmt_block, | |
| 64 | ||
| 65 | pub fn Data(comptime tag: Tag) type { | |
| 66 | return switch (tag) { | |
| 67 | .root => IndexList, | |
| 68 | .plain_value => PlainValue, | |
| 69 | .top_level_value => ValueRef, | |
| 70 | .local => LocalRef, | |
| 71 | .global => GlobalRef, | |
| 72 | .assign, | |
| 73 | .assign_add, | |
| 74 | .assign_sub, | |
| 75 | .assign_mul, | |
| 76 | .assign_div, | |
| 77 | .add, | |
| 78 | .sub, | |
| 79 | .mul, | |
| 80 | .div, | |
| 81 | .eq, | |
| 82 | .lt, | |
| 83 | .gt, | |
| 84 | => Bin, | |
| 85 | .ternary => Ternary, | |
| 86 | .negate, | |
| 87 | .bool_not, | |
| 88 | .pre_inc, | |
| 89 | .pre_dec, | |
| 90 | => Un, | |
| 91 | .object_literal => ObjectLiteral, | |
| 92 | .call, .new => Call, | |
| 93 | .field => Field, | |
| 94 | .stmt_if => StmtIf, | |
| 95 | .stmt_break => StmtBreak, | |
| 96 | .stmt_return => StmtReturn, | |
| 97 | .stmt_declare_local => StmtDeclareLocal, | |
| 98 | .stmt_block => StmtBlock, | |
| 99 | }; | |
| 100 | } | |
| 101 | }; | |
| 102 | ||
| 103 | pub const Node = struct { | |
| 104 | tag: Tag, | |
| 105 | data: EncodedData, | |
| 106 | ||
| 107 | pub const List = std.MultiArrayList(Node); | |
| 108 | ||
| 109 | pub const Index = enum(u32) { | |
| 110 | root = 0, | |
| 111 | _, | |
| 112 | ||
| 113 | pub fn toOptional(i: Index) Optional { | |
| 114 | return @enumFromInt(@intFromEnum(i)); | |
| 115 | } | |
| 116 | ||
| 117 | pub const Optional = enum(u32) { | |
| 118 | none = std.math.maxInt(u32), | |
| 119 | _, | |
| 120 | ||
| 121 | pub fn unwrap(oi: Optional) ?Index { | |
| 122 | return if (oi == .none) | |
| 123 | null | |
| 124 | else | |
| 125 | @enumFromInt(@intFromEnum(oi)); | |
| 126 | } | |
| 127 | }; | |
| 128 | }; | |
| 129 | }; | |
| 130 | ||
| 131 | pub const PlainValue = struct { | |
| 132 | value: Value, | |
| 133 | unused: Unused = .zero, | |
| 134 | }; | |
| 135 | ||
| 136 | pub const LocalRef = struct { | |
| 137 | id: LocalIndex, | |
| 138 | unused: Unused = .zero, | |
| 139 | }; | |
| 140 | ||
| 141 | pub const GlobalRef = struct { | |
| 142 | name: InternPool.String.WithLen, | |
| 143 | }; | |
| 144 | ||
| 145 | pub const ValueRef = struct { | |
| 146 | value: Value, | |
| 147 | unused: Unused = .zero, | |
| 148 | }; | |
| 149 | ||
| 150 | pub const StmtDeclareLocal = struct { | |
| 151 | identifier_index: u32, | |
| 152 | init: Node.Index.Optional, | |
| 153 | }; | |
| 154 | ||
| 155 | pub const Bin = struct { | |
| 156 | lhs: Node.Index, | |
| 157 | rhs: Node.Index, | |
| 158 | }; | |
| 159 | ||
| 160 | pub const Un = struct { | |
| 161 | operand: Node.Index, | |
| 162 | unused: Unused = .zero, | |
| 163 | }; | |
| 164 | ||
| 165 | pub const Call = struct { | |
| 166 | base: Node.Index, | |
| 167 | args: ExtraIndex(IndexList), | |
| 168 | }; | |
| 169 | ||
| 170 | pub const Field = struct { | |
| 171 | base: Node.Index, | |
| 172 | field: NullTerminatedString, | |
| 173 | }; | |
| 174 | ||
| 175 | /// Extra list is `property_count * 2` items long, with the first half being | |
| 176 | /// `ObjectLiteral.Key`s, and the second being value nodes. | |
| 177 | pub const ObjectLiteral = struct { | |
| 178 | extra_index: u32, | |
| 179 | property_count: u32, | |
| 180 | ||
| 181 | pub const Key = packed struct(u32) { | |
| 182 | kind: Kind, | |
| 183 | data: u31, | |
| 184 | ||
| 185 | pub const Kind = enum(u1) { | |
| 186 | static, | |
| 187 | computed, | |
| 188 | }; | |
| 189 | ||
| 190 | pub const Decoded = union(enum) { | |
| 191 | static: NullTerminatedString, | |
| 192 | computed: Node.Index, | |
| 193 | ||
| 194 | pub fn encode(decoded: Decoded) Key { | |
| 195 | return switch (decoded) { | |
| 196 | inline else => |data, tag| .{ | |
| 197 | .kind = @field(Kind, @tagName(tag)), | |
| 198 | .data = @intCast(@intFromEnum(data)), | |
| 199 | }, | |
| 200 | }; | |
| 201 | } | |
| 202 | }; | |
| 203 | ||
| 204 | pub fn decode(key: Key) Decoded { | |
| 205 | return switch (key.kind) { | |
| 206 | .static => .{ .static = @enumFromInt(key.data) }, | |
| 207 | .computed => .{ .computed = @enumFromInt(key.data) }, | |
| 208 | }; | |
| 209 | } | |
| 210 | }; | |
| 211 | }; | |
| 212 | ||
| 213 | pub const IndexList = struct { | |
| 214 | start: u32, | |
| 215 | len: u32, | |
| 216 | ||
| 217 | pub fn slice(list: IndexList, tree: *const Tree) []const Node.Index { | |
| 218 | return @ptrCast(tree.extra.list.items[list.start..][0..list.len]); | |
| 219 | } | |
| 220 | }; | |
| 221 | ||
| 222 | pub const StmtIf = struct { | |
| 223 | cond: Node.Index, | |
| 224 | data: ExtraIndex(Data), | |
| 225 | ||
| 226 | pub const Data = struct { | |
| 227 | yes: IndexList, | |
| 228 | no: IndexList, | |
| 229 | flags: Flags, | |
| 230 | }; | |
| 231 | ||
| 232 | pub const Flags = packed struct(u32) { | |
| 233 | block_label: u16, | |
| 234 | yes_label: bool, | |
| 235 | no_label: bool, | |
| 236 | _: u14 = 0, | |
| 237 | }; | |
| 238 | }; | |
| 239 | ||
| 240 | pub const Ternary = struct { | |
| 241 | cond: Node.Index, | |
| 242 | data: ExtraIndex(Data), | |
| 243 | ||
| 244 | pub const Data = struct { | |
| 245 | yes: Node.Index, | |
| 246 | no: Node.Index, | |
| 247 | }; | |
| 248 | }; | |
| 249 | ||
| 250 | pub const StmtBreak = struct { | |
| 251 | label: u32, | |
| 252 | unused: Unused = .zero, | |
| 253 | }; | |
| 254 | ||
| 255 | pub const StmtReturn = struct { | |
| 256 | operand: Node.Index.Optional, | |
| 257 | unused: Unused = .zero, | |
| 258 | }; | |
| 259 | ||
| 260 | pub const StmtBlock = struct { | |
| 261 | label: u32, | |
| 262 | content: ExtraIndex(IndexList), | |
| 263 | }; | |
| 264 | ||
| 265 | pub fn deinit(tree: *Tree, gpa: std.mem.Allocator) void { | |
| 266 | tree.nodes.deinit(gpa); | |
| 267 | gpa.free(tree.top_level_value_deps); | |
| 268 | gpa.free(tree.extern_symbols); | |
| 269 | tree.extra.deinit(gpa); | |
| 270 | } | |
| 271 | ||
| 272 | pub fn nodeTag(tree: Tree, index: Node.Index) Tag { | |
| 273 | return tree.nodes.items(.tag)[@intFromEnum(index)]; | |
| 274 | } | |
| 275 | ||
| 276 | pub fn nodeData(tree: Tree, comptime tag: Tag, index: Node.Index) tag.Data() { | |
| 277 | return tree.nodes.items(.data)[@intFromEnum(index)].decode(tag, tree.nodeTag(index)); | |
| 278 | } | |
| 279 | ||
| 280 | pub fn mainStatements(tree: *const Tree) []const Node.Index { | |
| 281 | return tree.nodeData(.root, .root).slice(tree); | |
| 282 | } | |
| 283 | ||
| 284 | const Tree = @This(); | |
| 285 | ||
| 286 | const std = @import("std"); | |
| 287 | const assert = std.debug.assert; | |
| 288 | ||
| 289 | const InternPool = @import("../InternPool.zig"); | |
| 290 | const Value = InternPool.Value; | |
| 291 | ||
| 292 | const NullTerminatedString = InternPool.NullTerminatedString; | |
| 293 | const String = InternPool.String; | |
| 294 | ||
| 295 | const Comp = @import("../Comp.zig"); | |
| 296 | const Decl = Comp.Decl; | |
| 297 | ||
| 298 | const extra = @import("../array.zig"); | |
| 299 | ||
| 300 | const Unused = @import("../Ast.zig").Unused; |
src/core/main.zig+5-2| ... | ... | @@ -101,11 +101,14 @@ pub fn main() !void { |
| 101 | 101 | std.process.exit(1); |
| 102 | 102 | }; |
| 103 | 103 | |
| 104 | var js_backend = core.Js{}; | |
| 105 | ||
| 104 | 106 | var comp: Comp = .{ |
| 105 | 107 | .gpa = tracer.tracingAllocator(&alloc), |
| 106 | 108 | .options = .{ |
| 107 | 109 | .dump_ir = dump_ir, |
| 108 | 110 | }, |
| 111 | .backend = .{ .js = &js_backend }, | |
| 109 | 112 | }; |
| 110 | 113 | try comp.init(); |
| 111 | 114 | defer comp.deinit(); |
| ... | ... | @@ -130,7 +133,7 @@ pub fn main() !void { |
| 130 | 133 | try teg.dumpToWriter(stderr); |
| 131 | 134 | } |
| 132 | 135 | |
| 133 | comp.semaEntryPoint(main_file) catch |err| switch (err) { | |
| 136 | const entry_point = comp.semaEntryPoint(main_file) catch |err| switch (err) { | |
| 134 | 137 | error.AnalysisFail => std.process.exit(1), |
| 135 | 138 | else => return err, |
| 136 | 139 | }; |
| ... | ... | @@ -138,7 +141,7 @@ pub fn main() !void { |
| 138 | 141 | const output = std.io.getStdOut().writer(); |
| 139 | 142 | var bufw = std.io.bufferedWriter(output); |
| 140 | 143 | const writer = bufw.writer(); |
| 141 | try comp.link(writer); | |
| 144 | try js_backend.link(&comp, entry_point, writer.any()); | |
| 142 | 145 | try bufw.flush(); |
| 143 | 146 | |
| 144 | 147 | std.process.cleanExit(); |
src/core/mod.zig+59-2| ... | ... | @@ -1,18 +1,75 @@ |
| 1 | //! Core language compiler module. | |
| 2 | ||
| 3 | // sort-lines: start | |
| 4 | ||
| 5 | /// Analyzed Intermediate Representation. | |
| 1 | 6 | pub const Air = @import("Air.zig"); |
| 7 | ||
| 2 | 8 | pub const array = @import("array.zig"); |
| 9 | ||
| 10 | /// Abstract Syntax Tree - Preserves all syntax forms. | |
| 11 | pub const Ast = @import("Ast.zig"); | |
| 12 | ||
| 3 | 13 | pub const Cache = @import("Cache.zig"); |
| 14 | ||
| 15 | /// Language Compilation Unit | |
| 4 | 16 | pub const Comp = @import("Comp.zig"); |
| 17 | ||
| 18 | /// Storage for shared objects (`Value`/`Decl`/`Namespace`) in a compilation | |
| 5 | 19 | pub const InternPool = @import("InternPool.zig"); |
| 6 | pub const JsLink = @import("JsLink.zig"); | |
| 20 | ||
| 21 | /// JavaScript backend and related utilities | |
| 22 | pub const Js = @import("js/Js.zig"); | |
| 23 | ||
| 24 | /// Recursive descent parser. Produces `Ast` | |
| 7 | 25 | pub const Parse = @import("Parse.zig"); |
| 26 | ||
| 8 | 27 | pub const Report = @import("Report.zig"); |
| 28 | ||
| 29 | /// Semantic Analysis visits a `Teg` declaration and produces `Air` | |
| 9 | 30 | pub const Sema = @import("Sema.zig"); |
| 31 | ||
| 32 | /// Untyped Intermediate Representation. Flat instruction list | |
| 10 | 33 | pub const Teg = @import("Teg.zig"); |
| 34 | ||
| 35 | /// Iterate tokens from a `[:0]const u8`. Includes code to decode/encode string escapes. | |
| 11 | 36 | pub const Tokenizer = @import("Tokenizer.zig"); |
| 37 | ||
| 38 | /// Transform pass converts `Ast` into `Teg` | |
| 12 | 39 | pub const Transform = @import("Transform.zig"); |
| 40 | ||
| 13 | 41 | pub const unicode_props = @import("unicode_props.zig"); |
| 42 | ||
| 14 | 43 | pub const util = @import("util.zig"); |
| 15 | 44 | |
| 45 | // sort-lines: end | |
| 46 | ||
| 47 | // The rest of this file is utility functions, which are not specific to any of | |
| 48 | // the above namespaces. Remember Zig discourages namespaces like `utils` | |
| 49 | ||
| 50 | /// This is an alternative to `std.debug.assert`, where you want to print some | |
| 51 | /// debug info instead of "reached unreachable code". Usage is as follows: | |
| 52 | /// | |
| 53 | /// if (assertFailed(variantIsTrue())) { | |
| 54 | /// std.debug.panic("...", .{ ... }); | |
| 55 | /// } | |
| 56 | /// | |
| 57 | /// This allows you to perform any number of side effects in the failure case | |
| 58 | /// with certainty that they are not analyzed in unsafe modes. | |
| 59 | pub inline fn assertFailed(cond: bool) bool { | |
| 60 | if (std.debug.runtime_safety) { | |
| 61 | return !cond; | |
| 62 | } else if (!cond) { | |
| 63 | unreachable; // assertion failure | |
| 64 | } | |
| 65 | } | |
| 66 | ||
| 67 | pub const detectTTYFromWriter = util.detectTTYFromWriter; | |
| 68 | pub const Ordinal = util.Ordinal; | |
| 69 | ||
| 70 | const std = @import("std"); | |
| 71 | const assert = std.debug.assert; | |
| 72 | ||
| 16 | 73 | comptime { |
| 17 | @import("std").testing.refAllDecls(@This()); | |
| 74 | std.testing.refAllDecls(@This()); | |
| 18 | 75 | } |
src/core/util.zig+2-9| ... | ... | @@ -1,3 +1,5 @@ |
| 1 | // TODO: move all decls in this file into mod.zig | |
| 2 | ||
| 1 | 3 | pub fn detectTTYFromWriter(writer: anytype) std.io.tty.Config { |
| 2 | 4 | if (@hasField(@TypeOf(writer), "unbuffered_writer")) { |
| 3 | 5 | return detectTTYFromWriter(writer.unbuffered_writer); |
| ... | ... | @@ -40,15 +42,6 @@ pub fn Ordinal(Int: type) type { |
| 40 | 42 | pub fn add(ord: @This(), inc: Int) @This() { |
| 41 | 43 | return fromZeroBased(ord.zeroBased() + inc); |
| 42 | 44 | } |
| 43 | ||
| 44 | pub fn format( | |
| 45 | _: @This(), | |
| 46 | comptime _: []const u8, | |
| 47 | _: std.fmt.FormatOptions, | |
| 48 | _: anytype, | |
| 49 | ) !void { | |
| 50 | // @compileError("Do not format `Ordinal` directly. Call oneBased or zeroBased to specify."); | |
| 51 | } | |
| 52 | 45 | }; |
| 53 | 46 | } |
| 54 | 47 |