| author | |
| committer | |
| log | 9ee226fafa7d8e90b97a395fbb16a370a83daa31 |
| tree | 29bf2fda9311ae600235b8b7349c10ae57905985 |
| parent | 4a2887aab0628d3b5b834049ea398d98f2d5549c |
| signature | Signed by PGP key B5690EEEBB952194 |
55 files changed, 5220 insertions(+), 2871 deletions(-)
CHANGELOG.md+8| ... | ... | @@ -1,8 +1,16 @@ |
| 1 | 1 | ## 0.1.6 |
| 2 | 2 | |
| 3 | - breaking: rename `@js` to `@extern`, considering future backends | |
| 4 | - re-write most of the semantic analysis pass (#79) | |
| 5 | - feat: implement function expressions | |
| 6 | - feat: implement simple `comptime` evaluation | |
| 7 | - feat: change all semantic analysis panics into handled errors | |
| 8 | - fix: no longer analyzes contents past a block end | |
| 3 | 9 | - feat: add `--run` to execute the file in QuickJS instead of outputting source |
| 4 | 10 | - feat: add array index syntax via `x[1]` and `x.1` |
| 5 | 11 | - feat: add `len` property to arrays |
| 12 | - feat: rename `noreturn` to `never` | |
| 13 | - feat: allow instantiating `@extern` | |
| 6 | 14 | - fix: crash when printing unused struct declaration error |
| 7 | 15 | - fix: provide array result type to sema |
| 8 | 16 | - fix: crash when analyzing an empty array without a result type |
README.md+10-9| ... | ... | @@ -1,20 +1,21 @@ |
| 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 | |
| 4 | JavaScript's great points. | |
| 3 | This is a high-level, general purpose programming language. It is a mix of Rust, | |
| 4 | Zig, and JavaScript's great points. | |
| 5 | 5 | |
| 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 | For a guide on how to use the language, please see the Language Manual included | |
| 7 | in the binary distribution. (TODO: host a rendered version) | |
| 8 | 8 | |
| 9 | 9 | ## Building |
| 10 | 10 | |
| 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: | |
| 11 | The primary dependency of the language SDK is a Zig compiler. If one is not | |
| 12 | available, the `./build` and `.\build.cmd` scripts can substitute for running | |
| 13 | `zig build` on POSIX and Windows systems, respectively. With a Zig compiler, a | |
| 14 | release build can be produced in a single command: | |
| 14 | 15 | |
| 15 | 16 | zig build --release |
| 16 | 17 | |
| 17 | To run the standard packaging procedure, which builds the SDK for all officially supported targets: | |
| 18 | To run the standard packaging procedure, which builds the SDK for all officially | |
| 19 | supported targets: | |
| 18 | 20 | |
| 19 | 21 | zig build package |
| 20 |
build.zig+429-189| ... | ... | @@ -1,8 +1,207 @@ |
| 1 | //! To learn more about the Zig build system: | |
| 2 | //! https://ziglang.org/learn/build-system/ | |
| 3 | //! | |
| 1 | 4 | //! See `architecture.md` for a codebase overview, and `zig-out/doc/README.html` |
| 2 | 5 | //! for information on the project. |
| 3 | 6 | |
| 7 | const BuildConfig = @import("src/BuildConfig.zig"); | |
| 8 | ||
| 9 | pub const Options = struct { | |
| 10 | target: Build.ResolvedTarget, | |
| 11 | optimize: std.builtin.OptimizeMode, | |
| 12 | ||
| 13 | /// Experiment with the Zig self-hosted backends. Build option '-Dno_llvm' | |
| 14 | no_llvm: bool, | |
| 15 | ||
| 16 | /// BuildConfig contains most of the configurable aspects of the compiler, | |
| 17 | /// and can be accessed in most modules with `@import("config")` | |
| 18 | config: BuildConfig, | |
| 19 | }; | |
| 20 | ||
| 21 | /// `createArtifactGroup` multiple times in order to have different | |
| 22 | /// targets and configurations of the SDK at the same time. | |
| 23 | const ArtifactGroup = struct { | |
| 24 | build: *Build, | |
| 25 | opts: Options, | |
| 26 | ||
| 27 | cli: *Step.Compile, | |
| 28 | format_cli: *Step.Compile, | |
| 29 | transform_cli: *Step.Compile, | |
| 30 | loader_cli: *Step.Compile, | |
| 31 | ||
| 32 | core_mod: *Module, | |
| 33 | loader_mod: *Module, | |
| 34 | config_mod: *Module, | |
| 35 | ||
| 36 | core_mod_tests: *Step.Compile, | |
| 37 | loader_mod_tests: *Step.Compile, | |
| 38 | compiler_tests: *Step.Compile, | |
| 39 | }; | |
| 40 | ||
| 41 | fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { | |
| 42 | const dep_opts = .{ .target = options.target, .optimize = options.optimize }; | |
| 43 | const use_llvm = if (options.no_llvm) false else null; | |
| 44 | const use_lld = if (options.no_llvm) false else null; | |
| 45 | ||
| 46 | // Dependencies | |
| 47 | const zg = b.dependency("zg", dep_opts); | |
| 48 | const tracer = b.dependency("tracer", dep_opts); | |
| 49 | const quickjs = b.dependency("quickjs", dep_opts); | |
| 50 | ||
| 51 | // Access build configuration via @import("config") | |
| 52 | const config_mod = config_mod: { | |
| 53 | const config = b.addOptions(); | |
| 54 | inline for (std.meta.fields(BuildConfig)) |field| { | |
| 55 | config.addOption(field.type, field.name, @field(options.config, field.name)); | |
| 56 | } | |
| 57 | config.addOption([]const u8, "version_string", b.fmt("{}", .{options.config.version})); | |
| 58 | break :config_mod config.createModule(); | |
| 59 | }; | |
| 60 | ||
| 61 | // Core | |
| 62 | const core_mod = b.addModule("core", .{ | |
| 63 | .root_source_file = b.path("src/core/mod.zig"), | |
| 64 | .optimize = options.optimize, | |
| 65 | .target = options.target, | |
| 66 | .strip = false, | |
| 67 | }); | |
| 68 | core_mod.addImport("config", config_mod); | |
| 69 | core_mod.addImport("tracer", tracer.module("tracer")); | |
| 70 | core_mod.addImport("zg:PropsData", zg.module("PropsData")); | |
| 71 | ||
| 72 | // Format | |
| 73 | const format_mod = b.addModule("format", .{ | |
| 74 | .root_source_file = b.path("src/format/Format.zig"), | |
| 75 | .optimize = options.optimize, | |
| 76 | .target = options.target, | |
| 77 | .strip = false, | |
| 78 | }); | |
| 79 | format_mod.addImport("core", core_mod); | |
| 80 | format_mod.addImport("config", config_mod); | |
| 81 | format_mod.addImport("tracer", tracer.module("tracer")); | |
| 82 | ||
| 83 | // Loader | |
| 84 | const loader_mod = b.addModule("loader", .{ | |
| 85 | .root_source_file = b.path("src/loader/Loader.zig"), | |
| 86 | .optimize = options.optimize, | |
| 87 | .target = options.target, | |
| 88 | .strip = false, | |
| 89 | }); | |
| 90 | loader_mod.addImport("core", core_mod); | |
| 91 | loader_mod.addImport("config", config_mod); | |
| 92 | ||
| 93 | // Core Unit Tests | |
| 94 | const core_mod_tests = createModuleTests(core_mod, options); | |
| 95 | const loader_mod_tests = createModuleTests(loader_mod, options); | |
| 96 | ||
| 97 | // Compiler test CLI | |
| 98 | const compiler_tests = b.addExecutable(.{ | |
| 99 | .name = "compiler_tests", | |
| 100 | .root_source_file = b.path("test/compiler.zig"), | |
| 101 | .target = options.target, | |
| 102 | .optimize = options.optimize, | |
| 103 | .use_lld = use_lld, | |
| 104 | .use_llvm = use_llvm, | |
| 105 | }); | |
| 106 | compiler_tests.root_module.addImport("core", core_mod); | |
| 107 | compiler_tests.root_module.addImport("config", config_mod); | |
| 108 | compiler_tests.root_module.addImport("tracer", tracer.module("tracer")); | |
| 109 | compiler_tests.root_module.addImport("quickjs", quickjs.module("quickjs")); | |
| 110 | ||
| 111 | // Main CLI | |
| 112 | const cli = b.addExecutable(.{ | |
| 113 | .name = "compiler", | |
| 114 | .root_source_file = b.path("src/cli.zig"), | |
| 115 | .target = options.target, | |
| 116 | .optimize = options.optimize, | |
| 117 | .use_lld = use_lld, | |
| 118 | .use_llvm = use_llvm, | |
| 119 | .single_threaded = true, | |
| 120 | }); | |
| 121 | cli.root_module.addImport("core", core_mod); | |
| 122 | cli.root_module.addImport("config", config_mod); | |
| 123 | cli.root_module.addImport("tracer", tracer.module("tracer")); | |
| 124 | if (options.config.have_embedded_quickjs) { | |
| 125 | cli.root_module.addImport("quickjs", quickjs.module("quickjs-libc")); | |
| 126 | } | |
| 127 | ||
| 128 | const format_cli = b.addExecutable(.{ | |
| 129 | .name = "format", | |
| 130 | .root_source_file = b.path("src/format/format_cli.zig"), | |
| 131 | .target = options.target, | |
| 132 | .optimize = options.optimize, | |
| 133 | .use_lld = use_lld, | |
| 134 | .use_llvm = use_llvm, | |
| 135 | .single_threaded = true, | |
| 136 | }); | |
| 137 | format_cli.root_module.addImport("core", core_mod); | |
| 138 | format_cli.root_module.addImport("format", format_mod); | |
| 139 | format_cli.root_module.addImport("config", config_mod); | |
| 140 | format_cli.root_module.addImport("tracer", tracer.module("tracer")); | |
| 141 | ||
| 142 | const loader_cli = b.addExecutable(.{ | |
| 143 | .name = "loader", | |
| 144 | .root_source_file = b.path("src/loader/main.zig"), | |
| 145 | .target = options.target, | |
| 146 | .optimize = options.optimize, | |
| 147 | .use_lld = use_lld, | |
| 148 | .use_llvm = use_llvm, | |
| 149 | .single_threaded = true, | |
| 150 | }); | |
| 151 | format_cli.root_module.addImport("loader", loader_mod); | |
| 152 | format_cli.root_module.addImport("config", config_mod); | |
| 153 | format_cli.root_module.addImport("tracer", tracer.module("tracer")); | |
| 154 | ||
| 155 | const transform_cli = b.addExecutable(.{ | |
| 156 | .name = "transform", | |
| 157 | .root_source_file = b.path("src/core/transform_cli.zig"), | |
| 158 | .target = options.target, | |
| 159 | .optimize = options.optimize, | |
| 160 | .use_lld = use_lld, | |
| 161 | .use_llvm = use_llvm, | |
| 162 | .single_threaded = true, | |
| 163 | }); | |
| 164 | transform_cli.root_module.addImport("core", core_mod); | |
| 165 | transform_cli.root_module.addImport("config", config_mod); | |
| 166 | transform_cli.root_module.addImport("tracer", tracer.module("tracer")); | |
| 167 | ||
| 168 | return .{ | |
| 169 | .build = b, | |
| 170 | .opts = options, | |
| 171 | ||
| 172 | .cli = cli, | |
| 173 | .format_cli = format_cli, | |
| 174 | .loader_cli = loader_cli, | |
| 175 | .transform_cli = transform_cli, | |
| 176 | ||
| 177 | .core_mod = core_mod, | |
| 178 | .loader_mod = loader_mod, | |
| 179 | .config_mod = config_mod, | |
| 180 | ||
| 181 | .core_mod_tests = core_mod_tests, | |
| 182 | .loader_mod_tests = loader_mod_tests, | |
| 183 | .compiler_tests = compiler_tests, | |
| 184 | }; | |
| 185 | } | |
| 186 | ||
| 187 | fn createModuleTests(module: *Build.Module, options: Options) *Build.Step.Compile { | |
| 188 | const tests = module.owner.addTest(.{ | |
| 189 | .root_source_file = module.root_source_file.?, | |
| 190 | .target = options.target, | |
| 191 | .optimize = options.optimize, | |
| 192 | .use_lld = if (options.no_llvm) false else null, | |
| 193 | .use_llvm = if (options.no_llvm) false else null, | |
| 194 | .strip = false, | |
| 195 | }); | |
| 196 | var it = module.import_table.iterator(); | |
| 197 | while (it.next()) |entry| { | |
| 198 | tests.root_module.addImport(entry.key_ptr.*, entry.value_ptr.*); | |
| 199 | } | |
| 200 | return tests; | |
| 201 | } | |
| 202 | ||
| 4 | 203 | pub fn build(b: *Build) !void { |
| 5 | b.reference_trace = 20; | |
| 204 | b.reference_trace = 32; | |
| 6 | 205 | |
| 7 | 206 | const target = b.standardTargetOptions(.{}); |
| 8 | 207 | |
| ... | ... | @@ -27,7 +226,7 @@ pub fn build(b: *Build) !void { |
| 27 | 226 | }; |
| 28 | 227 | |
| 29 | 228 | const tracing = b.option( |
| 30 | TracingMode, | |
| 229 | BuildConfig.TracingMode, | |
| 31 | 230 | "trace", |
| 32 | 231 | "Enable tracing", |
| 33 | 232 | ) orelse .none; |
| ... | ... | @@ -38,17 +237,11 @@ pub fn build(b: *Build) !void { |
| 38 | 237 | "Experiment with the Zig self-hosted backends", |
| 39 | 238 | ) orelse false; |
| 40 | 239 | |
| 41 | const version = version: { | |
| 42 | const text = try b.build_root.handle.readFileAlloc( | |
| 43 | b.graph.arena, | |
| 44 | "CHANGELOG.md", | |
| 45 | std.math.maxInt(usize), | |
| 46 | ); | |
| 47 | const first_header = std.mem.indexOf(u8, text, "## ") orelse unreachable; | |
| 48 | var tokenizer = std.mem.tokenizeAny(u8, text[first_header + 2 ..], " \t\n"); | |
| 49 | const version_text = tokenizer.next() orelse unreachable; | |
| 50 | break :version std.SemanticVersion.parse(version_text) catch unreachable; | |
| 51 | }; | |
| 240 | const have_embedded_quickjs = b.option( | |
| 241 | bool, | |
| 242 | "quickjs", | |
| 243 | "Build with QuickJS features", | |
| 244 | ) orelse true; | |
| 52 | 245 | |
| 53 | 246 | const js_runtime: PathOrString = if (b.option( |
| 54 | 247 | []const u8, |
| ... | ... | @@ -63,14 +256,126 @@ pub fn build(b: *Build) !void { |
| 63 | 256 | .optimize = .ReleaseSafe, |
| 64 | 257 | }).artifact("qjs").getEmittedBin() }; |
| 65 | 258 | |
| 66 | const native_debug = buildWithTarget(b, b.graph.host, .Debug, .none, version, no_llvm); | |
| 67 | const main = buildWithTarget(b, target, optimize, tracing, version, no_llvm); | |
| 259 | // This code is taken from Zig | |
| 260 | const version_override = b.option( | |
| 261 | []const u8, | |
| 262 | "version", | |
| 263 | "Override version string. Default is to find out with CHANGELOG.md and Git.", | |
| 264 | ); | |
| 265 | const version = if (version_override) |version| | |
| 266 | std.SemanticVersion.parse(version) catch { | |
| 267 | std.debug.panic("Invalid -Dversion value \"{s}\"", .{version}); | |
| 268 | } | |
| 269 | else version: { | |
| 270 | const text = try b.build_root.handle.readFileAlloc( | |
| 271 | b.graph.arena, | |
| 272 | "CHANGELOG.md", | |
| 273 | std.math.maxInt(usize), | |
| 274 | ); | |
| 275 | const first_header = std.mem.indexOf(u8, text, "## ") orelse @panic("Invalid changelog"); | |
| 276 | var tokenizer = std.mem.tokenizeAny(u8, text[first_header + 2 ..], " \t\n"); | |
| 277 | const version_text = tokenizer.next() orelse @panic("Invalid changelog"); | |
| 278 | var version = std.SemanticVersion.parse(version_text) catch | |
| 279 | std.debug.panic("Invalid version: {s}", .{version_text}); | |
| 280 | ||
| 281 | if (!std.process.can_spawn) { | |
| 282 | std.debug.print("error: version info cannot be retrieved from git. Version must be provided using -Dversion\n", .{}); | |
| 283 | std.process.exit(1); | |
| 284 | } | |
| 68 | 285 | |
| 69 | b.installArtifact(main.cli(b, .all)); | |
| 286 | var code: u8 = undefined; | |
| 287 | const git_describe_untrimmed = b.runAllowFail(&[_][]const u8{ | |
| 288 | "git", | |
| 289 | "-C", | |
| 290 | b.build_root.path orelse ".", | |
| 291 | "describe", | |
| 292 | "--match", | |
| 293 | "*.*.*", | |
| 294 | "--tags", | |
| 295 | "--abbrev=9", | |
| 296 | }, &code, .Ignore) catch { | |
| 297 | break :version version; | |
| 298 | }; | |
| 299 | const git_describe = std.mem.trim(u8, git_describe_untrimmed, " \n\r"); | |
| 300 | ||
| 301 | switch (std.mem.count(u8, git_describe, "-")) { | |
| 302 | 0 => { | |
| 303 | // Tagged release version (e.g. 0.10.0). | |
| 304 | if (!std.mem.eql(u8, git_describe, b.fmt("{}", .{version}))) { | |
| 305 | std.debug.print("Version '{}' does not match Git tag '{s}'\n", .{ version, git_describe }); | |
| 306 | std.process.exit(1); | |
| 307 | } | |
| 308 | break :version version; | |
| 309 | }, | |
| 310 | 2 => { | |
| 311 | // Untagged development build (e.g. 0.10.0-dev.2025+ecf0050a9). | |
| 312 | var it = std.mem.splitScalar(u8, git_describe, '-'); | |
| 313 | const tagged_ancestor = it.first(); | |
| 314 | const commit_height = it.next().?; | |
| 315 | const commit_id = it.next().?; | |
| 316 | ||
| 317 | const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor); | |
| 318 | if (version.order(ancestor_ver) != .gt) { | |
| 319 | std.debug.print("Version '{}' must be greater than tagged ancestor '{}'\n", .{ version, ancestor_ver }); | |
| 320 | std.process.exit(1); | |
| 321 | } | |
| 322 | ||
| 323 | // Check that the commit hash is prefixed with a 'g' (a Git convention). | |
| 324 | if (commit_id.len < 1 or commit_id[0] != 'g') { | |
| 325 | std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe}); | |
| 326 | break :version version; | |
| 327 | } | |
| 328 | ||
| 329 | // The version is reformatted in accordance with the https://semver.org specification. | |
| 330 | version.pre = b.fmt("dev.{s}", .{commit_height}); | |
| 331 | version.build = commit_id[1..]; | |
| 332 | ||
| 333 | break :version version; | |
| 334 | }, | |
| 335 | else => { | |
| 336 | std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe}); | |
| 337 | break :version version; | |
| 338 | }, | |
| 339 | } | |
| 340 | }; | |
| 341 | ||
| 342 | // This artifact group is configured for the host system, | |
| 343 | // for use in code generators + tests. | |
| 344 | const native_debug = createArtifactGroup(b, .{ | |
| 345 | .config = .{ .version = version }, | |
| 346 | .optimize = .Debug, | |
| 347 | .target = b.graph.host, | |
| 348 | .no_llvm = no_llvm, | |
| 349 | }); | |
| 350 | ||
| 351 | // This artifact group is used for the separated run steps, | |
| 352 | // each with a minimal configuration applied. | |
| 353 | const minimal = createArtifactGroup(b, .{ | |
| 354 | .config = .{ .version = version }, | |
| 355 | .optimize = optimize, | |
| 356 | .target = b.graph.host, | |
| 357 | .no_llvm = no_llvm, | |
| 358 | }); | |
| 359 | ||
| 360 | // This artifact group is configured for the build target. | |
| 361 | const main = createArtifactGroup(b, .{ | |
| 362 | .config = .{ | |
| 363 | .version = version, | |
| 364 | .tracing_mode = tracing, | |
| 365 | .have_embedded_quickjs = have_embedded_quickjs, | |
| 366 | }, | |
| 367 | .target = target, | |
| 368 | .optimize = optimize, | |
| 369 | .no_llvm = no_llvm, | |
| 370 | }); | |
| 371 | ||
| 372 | b.installArtifact(main.cli); | |
| 70 | 373 | |
| 71 | 374 | inline for (.{ |
| 72 | .{ main.cli(b, .minimal), "compiler" }, | |
| 73 | .{ main.loader_bin, "loader" }, | |
| 375 | .{ minimal.cli, "compiler" }, | |
| 376 | .{ minimal.format_cli, "format" }, | |
| 377 | .{ minimal.loader_cli, "loader" }, | |
| 378 | .{ minimal.transform_cli, "transform" }, | |
| 74 | 379 | }) |entry| { |
| 75 | 380 | const artifact, const name = entry; |
| 76 | 381 | |
| ... | ... | @@ -85,8 +390,9 @@ pub fn build(b: *Build) !void { |
| 85 | 390 | |
| 86 | 391 | const test_all = b.step("test", "Run all tests"); |
| 87 | 392 | inline for (.{ |
| 88 | .{ main.core_tests, "core", "core module" }, | |
| 89 | .{ main.behavior_tests, "behavior", "compiler behavior" }, | |
| 393 | .{ main.core_mod_tests, "core", "core module" }, | |
| 394 | .{ main.loader_mod_tests, "loader", "loader module" }, | |
| 395 | .{ main.compiler_tests, "compiler", "compiler behavior" }, | |
| 90 | 396 | }) |entry| { |
| 91 | 397 | const artifact, const name, const description = entry; |
| 92 | 398 | const run_cmd = b.addRunArtifact(artifact); |
| ... | ... | @@ -107,10 +413,11 @@ pub fn build(b: *Build) !void { |
| 107 | 413 | .optimize = .Debug, |
| 108 | 414 | .link_libc = true, |
| 109 | 415 | }); |
| 110 | manual_generator.root_module.addImport("build_options", native_debug.opts_mod); | |
| 111 | manual_generator.root_module.addImport("core", native_debug.core); | |
| 416 | manual_generator.root_module.addImport("config", native_debug.config_mod); | |
| 417 | manual_generator.root_module.addImport("core", native_debug.core_mod); | |
| 418 | ||
| 112 | 419 | const manual_run = b.addRunArtifact(manual_generator); |
| 113 | manual_run.addFileArg(native_debug.cli(b, BuildConfig.minimal).getEmittedBin()); | |
| 420 | manual_run.addFileArg(native_debug.cli.getEmittedBin()); | |
| 114 | 421 | switch (js_runtime) { |
| 115 | 422 | .path => |path| manual_run.addFileArg(path), |
| 116 | 423 | .string => |str| manual_run.addArg(str), |
| ... | ... | @@ -119,53 +426,79 @@ pub fn build(b: *Build) !void { |
| 119 | 426 | manual_run.addFileArg(b.path("src/manual/template.html")); |
| 120 | 427 | const manual_out = manual_run.addOutputFileArg("language-reference.html"); |
| 121 | 428 | const install_manual = b.addInstallFile(manual_out, "doc/README.html"); |
| 429 | ||
| 122 | 430 | b.step("manual", "build language reference").dependOn(&install_manual.step); |
| 123 | 431 | |
| 124 | // const wasm_api = b.addExecutable(.{ | |
| 125 | // .name = "compiler", | |
| 126 | // .root_source_file = b.path("src/wasm_api.zig"), | |
| 127 | // .target = b.resolveTargetQuery(.{ | |
| 128 | // .cpu_arch = .wasm32, | |
| 129 | // .os_tag = .freestanding, | |
| 130 | // }), | |
| 131 | // .optimize = optimize, | |
| 132 | // }); | |
| 133 | // wasm_api.root_module.export_symbol_names = &[_][]const u8{ | |
| 134 | // "iterateTokens", | |
| 135 | // }; | |
| 136 | // wasm_api.entry = .disabled; | |
| 137 | // const wasm_step = b.step("wasm", "Build wasm api"); | |
| 138 | // b.installArtifact(wasm_api); | |
| 139 | // wasm_step.dependOn(&wasm_api.step); | |
| 140 | // wasm_api.root_module.addImport("zg:PropsData", zg.module("PropsData")); | |
| 432 | { | |
| 433 | const wasm_target = b.resolveTargetQuery(.{ | |
| 434 | .cpu_arch = .wasm32, | |
| 435 | .os_tag = .freestanding, | |
| 436 | }); | |
| 437 | const wasm_artifacts = createArtifactGroup(b, .{ | |
| 438 | .config = .{ | |
| 439 | .version = version, | |
| 440 | .have_embedded_quickjs = false, | |
| 441 | }, | |
| 442 | .target = wasm_target, | |
| 443 | .optimize = optimize, | |
| 444 | .no_llvm = no_llvm, | |
| 445 | }); | |
| 446 | const wasm_api = b.addExecutable(.{ | |
| 447 | .name = "sdk", | |
| 448 | .root_source_file = b.path("src/wasm/wasm_api.zig"), | |
| 449 | .target = wasm_target, | |
| 450 | .optimize = optimize, | |
| 451 | }); | |
| 452 | wasm_api.root_module.addImport("config", wasm_artifacts.config_mod); | |
| 453 | wasm_api.root_module.addImport("core", wasm_artifacts.core_mod); | |
| 454 | wasm_api.root_module.export_symbol_names = &.{ | |
| 455 | "iterateTokens", | |
| 456 | }; | |
| 457 | wasm_api.entry = .disabled; | |
| 458 | const wasm_step = b.step("wasm", "[WIP] Build wasm api"); | |
| 459 | b.installArtifact(wasm_api); | |
| 460 | wasm_step.dependOn(&wasm_api.step); | |
| 461 | ||
| 462 | const wasm_codegen = b.step("wasm-codegen", "[WIP] Build wasm codegen"); | |
| 463 | const wasm_codegen_bin = b.addExecutable(.{ | |
| 464 | .name = "sdk", | |
| 465 | .root_source_file = b.path("src/wasm/enum_codegen.zig"), | |
| 466 | .target = b.graph.host, | |
| 467 | .optimize = .Debug, | |
| 468 | }); | |
| 469 | wasm_codegen_bin.root_module.addImport("core", native_debug.core_mod); | |
| 470 | const run_wasm_codegen = b.addRunArtifact(wasm_codegen_bin); | |
| 471 | wasm_codegen.dependOn(&run_wasm_codegen.step); | |
| 472 | } | |
| 141 | 473 | |
| 142 | 474 | // Packaging |
| 143 | 475 | const package = b.step("package", "Package a release build"); |
| 144 | const linux_version: OsVersion = .{ .semver = .{ .major = 1, .minor = 0, .patch = 0 } }; | |
| 145 | const mac_version: OsVersion = .{ .semver = .{ .major = 11, .minor = 0, .patch = 0 } }; | |
| 146 | const win_version: OsVersion = .{ .windows = .win7 }; | |
| 147 | 476 | |
| 148 | inline for (.{ | |
| 149 | b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version }), | |
| 150 | b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version }), | |
| 151 | b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .macos, .os_version_min = mac_version }), | |
| 152 | b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .macos, .os_version_min = mac_version }), | |
| 153 | b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu }), | |
| 154 | b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu }), | |
| 477 | for ([_]Build.ResolvedTarget{ | |
| 478 | crossTarget(.x86_64, .linux), | |
| 479 | crossTarget(.aarch64, .linux), | |
| 480 | crossTarget(.x86_64, .macos), | |
| 481 | crossTarget(.aarch64, .macos), | |
| 482 | crossTarget(.x86_64, .windows), | |
| 483 | crossTarget(.aarch64, .windows), | |
| 155 | 484 | }) |cross_target| { |
| 156 | const name = std.fmt.allocPrint( | |
| 157 | b.graph.arena, | |
| 158 | "{s}-{s}", | |
| 159 | .{ | |
| 160 | @tagName(cross_target.result.cpu.arch), | |
| 161 | @tagName(cross_target.result.os.tag), | |
| 162 | }, | |
| 163 | ) catch @panic("OOM"); | |
| 485 | const name = b.fmt("{s}-{s}", .{ | |
| 486 | @tagName(cross_target.result.cpu.arch), | |
| 487 | @tagName(cross_target.result.os.tag), | |
| 488 | }); | |
| 164 | 489 | |
| 165 | const artifacts = buildWithTarget(b, cross_target, .ReleaseSafe, .none, version, no_llvm); | |
| 490 | const artifacts = createArtifactGroup(b, .{ | |
| 491 | .config = .{ | |
| 492 | .version = version, | |
| 493 | .have_embedded_quickjs = have_embedded_quickjs, | |
| 494 | }, | |
| 495 | .optimize = optimize, | |
| 496 | .target = cross_target, | |
| 497 | .no_llvm = no_llvm, | |
| 498 | }); | |
| 166 | 499 | |
| 167 | 500 | inline for (.{ |
| 168 | .{ artifacts.cli(b, BuildConfig.all), "compiler" }, | |
| 501 | .{ artifacts.cli, "compiler" }, | |
| 169 | 502 | }) |entry| { |
| 170 | 503 | const artifact, const bin_name = entry; |
| 171 | 504 | package.dependOn(&b.addInstallFile( |
| ... | ... | @@ -173,10 +506,10 @@ pub fn build(b: *Build) !void { |
| 173 | 506 | b.pathJoin(&.{ |
| 174 | 507 | "package", |
| 175 | 508 | name, |
| 176 | std.mem.concat(b.graph.arena, u8, &.{ | |
| 509 | b.fmt("{s}{s}", .{ | |
| 177 | 510 | bin_name, |
| 178 | 511 | cross_target.result.exeFileExt(), |
| 179 | }) catch @panic("OOM"), | |
| 512 | }), | |
| 180 | 513 | }), |
| 181 | 514 | ).step); |
| 182 | 515 | } |
| ... | ... | @@ -184,140 +517,46 @@ pub fn build(b: *Build) !void { |
| 184 | 517 | package.dependOn(&b.addInstallFile(b.path("CHANGELOG.md"), "package/CHANGELOG.md").step); |
| 185 | 518 | package.dependOn(&b.addInstallFile(manual_out, "package/README.html").step); |
| 186 | 519 | } |
| 187 | } | |
| 188 | ||
| 189 | const TracingMode = enum { none, spall, chrome, log }; | |
| 190 | ||
| 191 | const Artifacts = struct { | |
| 192 | no_llvm: bool, | |
| 193 | target: Build.ResolvedTarget, | |
| 194 | optimize: std.builtin.OptimizeMode, | |
| 195 | 520 | |
| 196 | core: *Build.Module, | |
| 197 | core_tests: *Build.Step.Compile, | |
| 198 | opts_mod: *Build.Module, | |
| 199 | behavior_tests: *Build.Step.Compile, | |
| 200 | loader_bin: *Build.Step.Compile, | |
| 201 | ||
| 202 | /// Construct a CLI with certain features. | |
| 203 | /// When developing on a small component, such as the compiler core, you | |
| 204 | /// don't want to compile every part of the compiler. This returns a CLI | |
| 205 | /// compilation configured with only the features that are desired. | |
| 206 | fn cli(a: Artifacts, b: *Build, config: BuildConfig) *Build.Step.Compile { | |
| 207 | const dep_opts = .{ .target = a.target, .optimize = a.optimize }; | |
| 208 | const use_llvm = if (a.no_llvm) false else null; | |
| 209 | const use_lld = if (a.no_llvm) false else null; | |
| 210 | ||
| 211 | // Options | |
| 212 | const build_config = b.addOptions(); | |
| 213 | build_config.addOption(BuildConfig, "config", config); | |
| 214 | const build_config_mod = build_config.createModule(); | |
| 215 | ||
| 216 | const exe = b.addExecutable(.{ | |
| 217 | .name = "compiler", | |
| 218 | .root_source_file = b.path("src/cli.zig"), | |
| 219 | .target = a.target, | |
| 220 | .optimize = a.optimize, | |
| 221 | .use_lld = use_lld, | |
| 222 | .use_llvm = use_llvm, | |
| 223 | .strip = false, | |
| 224 | .omit_frame_pointer = false, | |
| 225 | .single_threaded = true, | |
| 226 | }); | |
| 227 | exe.root_module.addImport("build_options", a.opts_mod); | |
| 228 | exe.root_module.addImport("build_config", build_config_mod); | |
| 229 | exe.root_module.addImport("core", a.core); | |
| 230 | exe.root_module.addImport("tracer", b.dependency("tracer", dep_opts).module("tracer")); | |
| 231 | ||
| 232 | if (config.have_embedded_quickjs) { | |
| 233 | exe.root_module.addImport("quickjs", b.dependency("quickjs", dep_opts).module("quickjs_libc")); | |
| 234 | } | |
| 521 | { | |
| 522 | _ = b.top_level_steps.swapRemove("uninstall"); // this step does not work | |
| 235 | 523 | |
| 236 | return exe; | |
| 524 | const all_step = b.step("all", "Execute all of the above"); | |
| 525 | const steps = b.top_level_steps.values(); | |
| 526 | for (steps[0 .. steps.len - 1]) |step| all_step.dependOn(&step.step); | |
| 237 | 527 | } |
| 238 | }; | |
| 239 | ||
| 240 | fn buildWithTarget( | |
| 241 | b: *Build, | |
| 242 | target: Build.ResolvedTarget, | |
| 243 | optimize: std.builtin.OptimizeMode, | |
| 244 | tracing: TracingMode, | |
| 245 | version: std.SemanticVersion, | |
| 246 | no_llvm: bool, | |
| 247 | ) Artifacts { | |
| 248 | const dep_opts = .{ .target = target, .optimize = optimize }; | |
| 249 | const use_llvm = if (no_llvm) false else null; | |
| 250 | const use_lld = if (no_llvm) false else null; | |
| 251 | ||
| 252 | // Dependencies | |
| 253 | const zg = b.dependency("zg", dep_opts); | |
| 254 | const tracer = b.dependency("tracer", dep_opts); | |
| 255 | ||
| 256 | // Options | |
| 257 | const opts = b.addOptions(); | |
| 258 | opts.addOption(std.SemanticVersion, "version", version); | |
| 259 | opts.addOption([]const u8, "version_string", b.fmt("{}", .{version})); | |
| 260 | opts.addOption([]const u8, "tracing", @tagName(tracing)); | |
| 261 | const opts_mod = opts.createModule(); | |
| 262 | ||
| 263 | // Core | |
| 264 | const core = b.addModule("core", .{ | |
| 265 | .root_source_file = b.path("src/core/mod.zig"), | |
| 266 | .optimize = optimize, | |
| 267 | .target = target, | |
| 268 | .strip = false, | |
| 269 | }); | |
| 270 | core.addImport("build_options", opts_mod); | |
| 271 | core.addImport("tracer", tracer.module("tracer")); | |
| 272 | core.addImport("zg:PropsData", zg.module("PropsData")); | |
| 528 | } | |
| 273 | 529 | |
| 274 | // Core Unit Tests | |
| 275 | const core_tests = b.addTest(.{ | |
| 276 | .root_source_file = b.path("src/core/mod.zig"), | |
| 277 | .target = target, | |
| 278 | .optimize = optimize, | |
| 279 | .use_lld = use_lld, | |
| 280 | .use_llvm = use_llvm, | |
| 281 | .strip = false, | |
| 282 | }); | |
| 283 | core_tests.root_module.addImport("build_options", opts_mod); | |
| 284 | core_tests.root_module.addImport("tracer", tracer.module("tracer")); | |
| 285 | core_tests.root_module.addImport("zg:PropsData", zg.module("PropsData")); | |
| 530 | fn crossTarget(cpu_arch: std.Target.Cpu.Arch, os_tag: std.Target.Os.Tag) Build.ResolvedTarget { | |
| 531 | const query: std.Target.Query = .{ | |
| 532 | .cpu_arch = cpu_arch, | |
| 533 | .os_tag = os_tag, | |
| 286 | 534 | |
| 287 | // Loader | |
| 288 | const loader = b.addExecutable(.{ | |
| 289 | .name = "loader", | |
| 290 | .root_source_file = b.path("src/loader/main.zig"), | |
| 291 | .target = target, | |
| 292 | .optimize = optimize, | |
| 293 | .use_lld = use_lld, | |
| 294 | .use_llvm = use_llvm, | |
| 295 | .strip = false, | |
| 296 | }); | |
| 535 | .cpu_model = switch (cpu_arch) { | |
| 536 | .aarch64 => switch (os_tag) { | |
| 537 | .macos => .{ .explicit = &std.Target.aarch64.cpu.apple_m1 }, | |
| 538 | else => .baseline, | |
| 539 | }, | |
| 540 | else => .baseline, | |
| 541 | }, | |
| 297 | 542 | |
| 298 | const behavior_tests = b.addExecutable(.{ | |
| 299 | .name = "run_behavior_tests", | |
| 300 | .root_source_file = b.path("test/behavior.zig"), | |
| 301 | .target = target, | |
| 302 | .optimize = optimize, | |
| 303 | .use_lld = use_lld, | |
| 304 | .use_llvm = use_llvm, | |
| 305 | }); | |
| 306 | behavior_tests.root_module.addImport("core", core); | |
| 307 | behavior_tests.root_module.addImport("build_options", opts_mod); | |
| 308 | behavior_tests.root_module.addImport("tracer", tracer.module("tracer")); | |
| 309 | behavior_tests.root_module.addImport("quickjs", b.dependency("quickjs", dep_opts).module("quickjs")); | |
| 543 | .os_version_min = switch (os_tag) { | |
| 544 | .linux => .{ .semver = .{ .major = 4, .minor = 19, .patch = 0 } }, | |
| 545 | .macos => .{ .semver = .{ .major = 11, .minor = 0, .patch = 0 } }, | |
| 546 | .windows => .{ .windows = .win7 }, | |
| 547 | else => @panic("unhandled"), | |
| 548 | }, | |
| 310 | 549 | |
| 550 | .abi = switch (os_tag) { | |
| 551 | .linux => .musl, | |
| 552 | .macos => .none, | |
| 553 | .windows => .gnu, // cannot cross compile .msvc | |
| 554 | else => @panic("unhandled"), | |
| 555 | }, | |
| 556 | }; | |
| 311 | 557 | return .{ |
| 312 | .no_llvm = no_llvm, | |
| 313 | .target = target, | |
| 314 | .optimize = optimize, | |
| 315 | ||
| 316 | .core = core, | |
| 317 | .core_tests = core_tests, | |
| 318 | .opts_mod = opts_mod, | |
| 319 | .behavior_tests = behavior_tests, | |
| 320 | .loader_bin = loader, | |
| 558 | .query = query, | |
| 559 | .result = std.zig.system.resolveTargetQuery(query) catch unreachable, | |
| 321 | 560 | }; |
| 322 | 561 | } |
| 323 | 562 | |
| ... | ... | @@ -328,7 +567,8 @@ pub const PathOrString = union(enum) { |
| 328 | 567 | |
| 329 | 568 | const std = @import("std"); |
| 330 | 569 | const Build = std.Build; |
| 570 | const Step = Build.Step; | |
| 571 | const Module = Build.Module; | |
| 331 | 572 | const LazyPath = Build.LazyPath; |
| 332 | 573 | const Target = std.Target; |
| 333 | 574 | const OsVersion = Target.Query.OsVersion; |
| 334 | const BuildConfig = @import("src/BuildConfig.zig"); |
build.zig.zon+4-1| ... | ... | @@ -2,17 +2,20 @@ |
| 2 | 2 | .name = "", |
| 3 | 3 | .version = "0.0.0", |
| 4 | 4 | |
| 5 | .minimum_zig_version = "0.13.0", | |
| 5 | .minimum_zig_version = "0.14.0-dev.1472+3929cac15", | |
| 6 | 6 | |
| 7 | 7 | .dependencies = .{ |
| 8 | // Used to determine unicode properties to tokenize identifiers | |
| 8 | 9 | .zg = .{ |
| 9 | 10 | .url = "https://codeberg.org/dude_the_builder/zg/archive/c425c9c8511bf92e14b8b612d1d16e774b186f2e.tar.gz", |
| 10 | 11 | .hash = "1220c79009f1e443b015db4de6e5319432538ead2fea10d271f3f35d3ae83c996789", |
| 11 | 12 | }, |
| 13 | // Optional: Used for tracing | |
| 12 | 14 | .tracer = .{ |
| 13 | 15 | .url = "https://github.com/paperdave/zig-tracer/archive/d4c2eff040e9e69b9f08d2175b315f3055ad7ba3.tar.gz", |
| 14 | 16 | .hash = "12202d60766a46ceb4074ce5651a58a11f2f6b32927476a55c6889805073e8e3d261", |
| 15 | 17 | }, |
| 18 | // Optional: Used for JavaScript behavior tests | |
| 16 | 19 | .quickjs = .{ |
| 17 | 20 | .path = "src/deps/quickjs", |
| 18 | 21 | }, |
doc/architecture.md+55-6| ... | ... | @@ -1,13 +1,62 @@ |
| 1 | 1 | # Architecture |
| 2 | 2 | |
| 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: | |
| 3 | This document will outline the code structure of the language's SDK as well as | |
| 4 | the compilation pipeline architecture. The major components of the language are: | |
| 4 | 5 | |
| 5 | - [**Core**][core] (`src/core`) | |
| 6 | - [**SDK Manual Generator**][manual] (`src/manual`) | |
| 6 | - [**Core**][#core] (`src/core`) includes the parser, analysis passes, codegen | |
| 7 | backends, and generating all warnings and errors. | |
| 8 | - [**Manual Generator**][#manual] (`src/manual`) is a custom static site | |
| 9 | generator used for building the SDK's documentation site. | |
| 7 | 10 | |
| 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`. | |
| 11 | Keep in mind that **code is the best documentation for specific details**, and | |
| 12 | this document aims to give an overall explainer of the codebase as a whole. | |
| 9 | 13 | |
| 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. | |
| 14 | General code style is to sort the file's contents by importance. Most files will | |
| 15 | start with an overview comment, followed by the root struct fields, and then | |
| 16 | important companion types and functions, then helpers. Imports and tests are not | |
| 17 | as important to a reader, so those are placed at the end of the file. | |
| 11 | 18 | |
| 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. | |
| 19 | An example of this organization is `src/core/Tokenizer.zig`, which opens with | |
| 20 | the tokenizer state, and then `pub fn tokenize` to bring attention to the flow | |
| 21 | of tokenization. The `Token` struct is directly after this, since it is | |
| 22 | important, but not as important as visualizing _how_ the tokenizer works. | |
| 13 | 23 | |
| 24 | ## Testing | |
| 25 | ||
| 26 | Aside from tests written using Zig's test runner, there are a large set of tests | |
| 27 | within `test` which are ran using a custom test runner binary. It categorizes | |
| 28 | compiler tests into these categories: | |
| 29 | ||
| 30 | - **Analysis Tests** (`test/analysis/*`) verify the semantic analysis | |
| 31 | engine, and must not emit any runtime `Air`. Some analysis tests may | |
| 32 | expect certain compile errors. | |
| 33 | - **Behavior Tests** (`test/behavior/*`) generate code to be executed at | |
| 34 | runtime, testing both the semantic analysis pass as well as the code | |
| 35 | generation backend. | |
| 36 | - **Transform Error Tests** (`test/transform_error`) confirm that the transform | |
| 37 | pass errors are correct. A passing `Teg` fails the test. | |
| 38 | ||
| 39 | All compiler tests can be run with `zig build test`, or the test suite can be | |
| 40 | filtered using flags, see `zig build test --help` | |
| 41 | ||
| 42 | To run Zig tests for an associated module, use `zig build test-{name}`, or run | |
| 43 | `zig build test-all` to run all tests in the codebase. | |
| 44 | ||
| 45 | ## Core | |
| 46 | ||
| 47 | The `core` module contains most of the compiler: | |
| 48 | ||
| 49 | - Tokenizing and Parsing files. (`Tokenizer.zig`, `Parse.zig`, `Ast.zig`) | |
| 50 | - Transform Pass, moving from Ast to an untyped intermediate representation | |
| 51 | (`Transform.zig`) | |
| 52 | - Semantic Analysis, resolving all types and comptime values. (`Sema.zig`) | |
| 53 | - Intern Pool, storing and de-duplicating objects in a compilation. | |
| 54 | (`InternPool.zig`) | |
| 55 | - JavaScript Code Generation (`js/Js.zig`) | |
| 56 | ||
| 57 | Note that components like the formatter and module fetching code are not here. | |
| 58 | Keeping this module as small as possible is important for keeping compile times | |
| 59 | low. Using `zig build compiler -- --help` instead of the fully featured binary | |
| 60 | will allow for faster and more focused iteration. | |
| 61 | ||
| 62 | ## Manual |
doc/language-manual.md+65-37| ... | ... | @@ -4,11 +4,14 @@ This is a high-level, general purpose programming language. |
| 4 | 4 | |
| 5 | 5 | Key goals of this project are: |
| 6 | 6 | |
| 7 | - **Ease of Use**: Use of type inference keeps code concise and easy to read and write. | |
| 8 | - **Fast Iteration**: The compiler is designed for quick incremental compilation and hot module reloading. (note: incremental is not yet available) | |
| 7 | - **Ease of Use**: Use of type inference keeps code concise and easy to read and | |
| 8 | write. | |
| 9 | - **Fast Iteration**: The compiler is designed for quick incremental compilation | |
| 10 | and hot module reloading. (note: incremental is not yet available) | |
| 9 | 11 | - TODO: some more |
| 10 | 12 | |
| 11 | It primarily focuses on merging some great points of Rust, Zig, and JavaScript's language designs: | |
| 13 | It primarily focuses on merging some great points of Rust, Zig, and JavaScript's | |
| 14 | language designs: | |
| 12 | 15 | |
| 13 | 16 | - **Rust** - Expressive control flow that is easy to write. |
| 14 | 17 | - **Zig** - Execute any code at compile time; everything is an expression. |
| ... | ... | @@ -21,29 +24,34 @@ Currently, there is no standard library. One will be introduced later. |
| 21 | 24 | ## Hello World |
| 22 | 25 | |
| 23 | 26 | ```lang:run |
| 24 | let print = @js("console").log; | |
| 25 | print("Hello, world!"); | |
| 27 | let print = @extern("console").log; | |
| 28 | ||
| 29 | fn main { | |
| 30 | print("Hello, world!"); | |
| 31 | } | |
| 26 | 32 | ``` |
| 27 | 33 | |
| 28 | The `print` function is implemented by getting an external reference to the `console` JavaScript global and then retrieving the `log` property from it. | |
| 34 | The `print` function is implemented by getting an external reference to the | |
| 35 | `console` JavaScript global and then retrieving the `log` property from it. | |
| 29 | 36 | |
| 30 | 37 | ## Primitive Data Types |
| 31 | 38 | |
| 32 | | name | ts equivalent | description | | |
| 33 | | ---------- | --------------------- | ---------------------------------------------- | | |
| 34 | | `int` | `number` | a number in signed 32-bit range | | |
| 35 | | `float` | `number` | a 64-bit floating point number | | |
| 36 | | `bool` | `boolean` | either `true` or `false` | | |
| 37 | | `string` | `string` | a sequence of text characters | | |
| 38 | | `void` | `undefined` or `null` | detectable lack of a value | | |
| 39 | | `opaque` | `unknown` or `any` | could be any value | | |
| 40 | | `noreturn` | `never` | control flow prevents this value from existing | | |
| 39 | | name | ts equivalent | description | | |
| 40 | | -------- | --------------------- | ---------------------------------------------- | | |
| 41 | | `int` | `number` | a number in signed 32-bit range | | |
| 42 | | `float` | `number` | a 64-bit floating point number | | |
| 43 | | `bool` | `boolean` | either `true` or `false` | | |
| 44 | | `string` | `string` | a sequence of text characters | | |
| 45 | | `void` | `undefined` or `null` | detectable lack of a value | | |
| 46 | | `opaque` | `unknown` or `any` | could be any value | | |
| 47 | | `never` | `never` | control flow prevents this value from existing | | |
| 41 | 48 | |
| 42 | 49 | > **note**: `bigint` is planned but not added |
| 43 | 50 | |
| 44 | 51 | ## Built-in Constants |
| 45 | 52 | |
| 46 | In addition to the above types being built-in constants, the following are available. | |
| 53 | In addition to the above types being built-in constants, the following are | |
| 54 | available. | |
| 47 | 55 | |
| 48 | 56 | | name | description | |
| 49 | 57 | | ------- | ------------------------------------- | |
| ... | ... | @@ -61,41 +69,49 @@ let true = 1 == 0; |
| 61 | 69 | |
| 62 | 70 | There are two types of variables, `let` and `var`. |
| 63 | 71 | |
| 64 | `let` bindings cannot be overridden, and should be thought of as aliases of the expression within. Referring to these identifiers is equivalent to inlining it where it was used, aside from side effect duplication. | |
| 72 | `let` bindings cannot be overridden, and should be thought of as aliases of the | |
| 73 | expression within. Referring to these identifiers is equivalent to inlining it | |
| 74 | where it was used, aside from side effect duplication. | |
| 65 | 75 | |
| 66 | 76 | ```lang:run |
| 67 | 77 | let hello: int = 1; |
| 68 | 78 | let world = hello + 2; |
| 69 | 79 | let hello = hello * world; |
| 70 | 80 | |
| 71 | @js("console").log(hello); | |
| 81 | @extern("console").log(hello); | |
| 72 | 82 | ``` |
| 73 | 83 | |
| 74 | `var` bindings create mutable memory locations at runtime. This means they can be re-assigned to: | |
| 84 | `var` bindings create mutable memory locations at runtime. This means they can | |
| 85 | be re-assigned to: | |
| 75 | 86 | |
| 76 | 87 | ```lang:run |
| 77 | 88 | var message = "first message"; |
| 78 | 89 | |
| 79 | if @js("Math").random() > 0.5 { | |
| 90 | if @extern("Math").random() > 0.5 { | |
| 80 | 91 | message = "second message"; |
| 81 | 92 | } |
| 82 | 93 | |
| 83 | @js("console").log(message); | |
| 94 | @extern("console").log(message); | |
| 84 | 95 | ``` |
| 85 | 96 | |
| 86 | Using `let` is preferred whenever possible, as it will lead to more readable code, and the compiler is able to optimize it better. The above can be rewritten as: | |
| 97 | Using `let` is preferred whenever possible, as it will lead to more readable | |
| 98 | code, and the compiler is able to optimize it better. The above can be rewritten | |
| 99 | as: | |
| 87 | 100 | |
| 88 | 101 | ```lang:run |
| 89 | let message = if @js("Math").random() > 0.5 { | |
| 102 | let message = if @extern("Math").random() > 0.5 { | |
| 90 | 103 | "second message" |
| 91 | 104 | } else { |
| 92 | 105 | "first message" |
| 93 | 106 | }; |
| 94 | 107 | |
| 95 | @js("console").log(message); | |
| 108 | @extern("console").log(message); | |
| 96 | 109 | ``` |
| 97 | 110 | |
| 98 | It should be noted that numeric literals are a comptime-only data type, meaning it cannot be assigned to a `var` without an explicit type annotation. Since `int` is a strict subset of valid `float`s, the coercion will implicitly be a float, but a compiler warning will be emitted. | |
| 111 | It should be noted that numeric literals are a comptime-only data type, meaning | |
| 112 | it cannot be assigned to a `var` without an explicit type annotation. Since | |
| 113 | `int` is a strict subset of valid `float`s, the coercion will implicitly be a | |
| 114 | float, but a compiler warning will be emitted. | |
| 99 | 115 | |
| 100 | 116 | ```lang |
| 101 | 117 | var number = 0; // `number` is a `float`, but not explicit |
| ... | ... | @@ -105,18 +121,21 @@ var number: float = 0; // `number` is `float` |
| 105 | 121 | |
| 106 | 122 | ## Functions |
| 107 | 123 | |
| 108 | Functions are declared with the `fn` keyword. Parameter types must be annotated. The function body is a block, which may use implicit return to return. | |
| 124 | Functions are declared with the `fn` keyword. Parameter types must be annotated. | |
| 125 | The function body is a block, which may use implicit return to return. | |
| 109 | 126 | |
| 110 | 127 | ```lang:run |
| 111 | 128 | fn add(a: int, b: int) -> int { |
| 112 | 129 | a + b |
| 113 | 130 | } |
| 114 | 131 | |
| 115 | let print = @js("console").log; | |
| 132 | let print = @extern("console").log; | |
| 116 | 133 | print("Sum of 2 and 7:", add(2, 7)); |
| 117 | 134 | ``` |
| 118 | 135 | |
| 119 | > **Bug**: Return types are currently required. Omitting the `-> type` syntax currently makes the return type `void`, but this will be changed in the future to be an inferred return type. | |
| 136 | > **Bug**: Return types are currently required. Omitting the `-> type` syntax | |
| 137 | > currently makes the return type `void`, but this will be changed in the future | |
| 138 | > to be an inferred return type. | |
| 120 | 139 | |
| 121 | 140 | The `return` expression can be used within functions to return early. |
| 122 | 141 | |
| ... | ... | @@ -129,7 +148,7 @@ fn something(a: int) -> bool { |
| 129 | 148 | a > 100 |
| 130 | 149 | } |
| 131 | 150 | |
| 132 | let print = @js("console").log; | |
| 151 | let print = @extern("console").log; | |
| 133 | 152 | print(something(0xFF), something(0)); |
| 134 | 153 | ``` |
| 135 | 154 | |
| ... | ... | @@ -143,28 +162,35 @@ TODO |
| 143 | 162 | |
| 144 | 163 | ## JavaScript Interop |
| 145 | 164 | |
| 146 | A global variable handle can be retrieved with `@js`. We've seen this in the hello world. The type of this value is a special type called `opaque`, which disables type-checking. | |
| 165 | A global variable handle can be retrieved with `@extern`. We've seen this in the | |
| 166 | hello world. The type of this value is a special type called `opaque`, which | |
| 167 | disables type-checking. | |
| 147 | 168 | |
| 148 | 169 | > **Note**: A future update will improve on the type-safety of `opaque` |
| 149 | 170 | |
| 150 | Many JS-exclusive actions are exposed via built-in functions, such as using `@jsNew` for calling constructors. | |
| 171 | Many JS-exclusive actions are exposed via built-in functions, such as using | |
| 172 | `@jsNew` for calling constructors. | |
| 151 | 173 | |
| 152 | 174 | ```lang:run |
| 153 | let print = @js("console").log; | |
| 175 | let print = @extern("console").log; | |
| 154 | 176 | |
| 155 | 177 | fn currentYear() -> int { |
| 156 | @jsNew(@js("Date")).getFullYear() | |
| 178 | @jsNew(@extern("Date")).getFullYear() | |
| 157 | 179 | } |
| 158 | 180 | |
| 159 | 181 | print(currentYear()); |
| 160 | 182 | ``` |
| 161 | 183 | |
| 184 | A field access on an external handle is allowed to be performed at compile time, | |
| 185 | where such access will only crash at runtime when the value is used. | |
| 186 | ||
| 162 | 187 | ## Structs |
| 163 | 188 | |
| 164 | To group data together, a `struct` can be used. A struct contains zero or one fields of runtime-compatible types. | |
| 189 | To group data together, a `struct` can be used. A struct contains zero or one | |
| 190 | fields of runtime-compatible types. | |
| 165 | 191 | |
| 166 | 192 | ```lang:run |
| 167 | let print = @js("console").log; | |
| 193 | let print = @extern("console").log; | |
| 168 | 194 | |
| 169 | 195 | struct Position { |
| 170 | 196 | x: int, |
| ... | ... | @@ -186,7 +212,9 @@ let added = addPositions( |
| 186 | 212 | print("Add result:", added.x, added.y); |
| 187 | 213 | ``` |
| 188 | 214 | |
| 189 | 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. | |
| 215 | To allow optimizations, `struct`s by default do not have a defined | |
| 216 | representation in JavaScript. This means passing the struct to an opaque | |
| 217 | function is not allowed. | |
| 190 | 218 | |
| 191 | 219 | ```lang:build |
| 192 | 220 | struct Person { |
| ... | ... | @@ -194,6 +222,6 @@ struct Person { |
| 194 | 222 | age: int, |
| 195 | 223 | } |
| 196 | 224 | |
| 197 | let print = @js("console").log; | |
| 225 | let print = @extern("console").log; | |
| 198 | 226 | print(Person{ name: "hello", age: 42 }); |
| 199 | 227 | ``` |
dprint.jsonc created+26| ... | ... | @@ -0,0 +1,26 @@ |
| 1 | { | |
| 2 | "plugins": [ | |
| 3 | // sort-lines: start | |
| 4 | "https://plugins.dprint.dev/exec-0.5.0.json@8d9972eee71fa1590e04873540421f3eda7674d0f1aae3d7c788615e7b7413d0", | |
| 5 | "https://plugins.dprint.dev/json-0.19.3.wasm", | |
| 6 | "https://plugins.dprint.dev/markdown-0.17.2.wasm", | |
| 7 | // sort-lines: end | |
| 8 | ], | |
| 9 | "excludes": [ | |
| 10 | "**/.zig-cache", | |
| 11 | "**/zig-out", | |
| 12 | "CHANGELOG.md", // no wrapping, one item per line | |
| 13 | ], | |
| 14 | "markdown": { | |
| 15 | "textWrap": "always", | |
| 16 | }, | |
| 17 | "exec": { | |
| 18 | "cwd": "${configDir}", | |
| 19 | "commands": [ | |
| 20 | { | |
| 21 | "command": "zig fmt --stdin", | |
| 22 | "exts": ["zig", "zon"], | |
| 23 | }, | |
| 24 | ], | |
| 25 | }, | |
| 26 | } |
hello created+6| ... | ... | @@ -0,0 +1,6 @@ |
| 1 | fn runtime() -> int { | |
| 2 | 542 | |
| 3 | } | |
| 4 | ||
| 5 | var abc: int = 0; | |
| 6 | abc = runtime(); |
out.js created+21| ... | ... | @@ -0,0 +1,21 @@ |
| 1 | // Language Compiler v0.1.6. Alpha quality software. | |
| 2 | let addFloat = (v0, v1) => v0 + v1; | |
| 3 | let f = v0 => v0; | |
| 4 | let subFloat = (v0, v1) => v0 - v1; | |
| 5 | let mulFloat = (v0, v1) => v0 * v1; | |
| 6 | let divFloat = (v0, v1) => v0 / v1; | |
| 7 | let addInt = (v0, v1) => v0 + v1; | |
| 8 | let i = v0 => v0; | |
| 9 | let subInt = (v0, v1) => v0 - v1; | |
| 10 | assertEqual(addFloat(1, 2), f(3)); | |
| 11 | assertEqual(addFloat(1.5, 2.2), f(3.7)); | |
| 12 | assertEqual(subFloat(1, 2), f(-1)); | |
| 13 | assertEqual(subFloat(1.5, 2.2), f(-0.7000000000000002)); | |
| 14 | assertEqual(mulFloat(1, 2), f(2)); | |
| 15 | assertEqual(mulFloat(1.5, 2.2), f(3.3000000000000003)); | |
| 16 | assertEqual(divFloat(1, 2), f(0.5)); | |
| 17 | assertEqual(divFloat(1.5, 2.2), f(0.6818181818181818)); | |
| 18 | assertEqual(addInt(1, 2), i(3)); | |
| 19 | assertEqual(addInt(-421, 592), i(171)); | |
| 20 | assertEqual(subInt(1, 2), i(-1)); | |
| 21 | assertEqual(subInt(528, -43), i(571)); |
src/BuildConfig.zig+8-4| ... | ... | @@ -1,9 +1,13 @@ |
| 1 | version: std.SemanticVersion, | |
| 2 | tracing_mode: TracingMode = .none, | |
| 1 | 3 | have_embedded_quickjs: bool = false, |
| 2 | 4 | |
| 3 | pub const minimal: BuildConfig = .{}; | |
| 4 | ||
| 5 | pub const all: BuildConfig = .{ | |
| 6 | .have_embedded_quickjs = true, | |
| 5 | pub const TracingMode = enum { | |
| 6 | none, | |
| 7 | spall, | |
| 8 | chrome, | |
| 9 | log, | |
| 7 | 10 | }; |
| 8 | 11 | |
| 9 | 12 | const BuildConfig = @This(); |
| 13 | const std = @import("std"); |
src/cli.zig+24-13| ... | ... | @@ -5,7 +5,7 @@ |
| 5 | 5 | //! needing to compile the formatter. |
| 6 | 6 | |
| 7 | 7 | const have_debug_features = builtin.mode == .Debug; |
| 8 | const config = @import("build_config").config; | |
| 8 | const config = @import("config"); | |
| 9 | 9 | |
| 10 | 10 | pub fn main() !u8 { |
| 11 | 11 | try tracer.init(); |
| ... | ... | @@ -24,7 +24,8 @@ pub fn main() !u8 { |
| 24 | 24 | }){} |
| 25 | 25 | else |
| 26 | 26 | std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| 27 | defer _ = if (use_gpa) gpa.deinit(); | |
| 27 | var no_leak_checks = false; | |
| 28 | defer _ = if (use_gpa and !no_leak_checks) gpa.deinit(); | |
| 28 | 29 | const alloc = gpa.allocator(); |
| 29 | 30 | |
| 30 | 31 | // Current working directory is required for CLI resolution. |
| ... | ... | @@ -48,9 +49,15 @@ pub fn main() !u8 { |
| 48 | 49 | |
| 49 | 50 | while (args.next()) |arg| { |
| 50 | 51 | if (std.mem.startsWith(u8, arg, "-")) { |
| 51 | if (have_debug_features and std.mem.eql(u8, arg, "--report-trace")) { | |
| 52 | core.Report.debug_show_report_trace = true; | |
| 53 | continue; | |
| 52 | if (have_debug_features) { | |
| 53 | if (std.mem.eql(u8, arg, "--report-trace")) { | |
| 54 | core.Report.debug_show_report_trace = true; | |
| 55 | continue; | |
| 56 | } | |
| 57 | if (std.mem.eql(u8, arg, "--no-leak-checks")) { | |
| 58 | no_leak_checks = true; | |
| 59 | continue; | |
| 60 | } | |
| 54 | 61 | } |
| 55 | 62 | |
| 56 | 63 | if (std.mem.eql(u8, arg, "--dump-ir")) { |
| ... | ... | @@ -107,9 +114,11 @@ pub fn main() !u8 { |
| 107 | 114 | \\ |
| 108 | 115 | \\Debug Mode Flags: |
| 109 | 116 | \\ --report-trace Print stack traces per error |
| 117 | \\ --no-leak-checks | |
| 110 | 118 | \\ |
| 111 | 119 | , .{}); |
| 112 | 120 | } |
| 121 | std.debug.print("\nLanguage SDK v" ++ config.version_string ++ "\n", .{}); | |
| 113 | 122 | return 1; |
| 114 | 123 | } |
| 115 | 124 | const file_path = maybe_file_path.?; |
| ... | ... | @@ -118,15 +127,13 @@ pub fn main() !u8 { |
| 118 | 127 | { |
| 119 | 128 | var js_backend = core.Js{}; |
| 120 | 129 | |
| 121 | var comp: core.Comp = .{ | |
| 130 | var comp = try core.Comp.init(.{ | |
| 122 | 131 | .gpa = tracer.tracingAllocator(&alloc), |
| 123 | 132 | .options = .{ |
| 124 | 133 | .dump_ir = dump_ir, |
| 125 | 134 | }, |
| 126 | 135 | .backend = .{ .js = &js_backend }, |
| 127 | .out = core.OutStream.init(std.io.getStdOut().writer().any()), | |
| 128 | }; | |
| 129 | try comp.init(); | |
| 136 | }); | |
| 130 | 137 | defer comp.deinit(); |
| 131 | 138 | |
| 132 | 139 | defer if (dump_state) |path| { |
| ... | ... | @@ -141,7 +148,7 @@ pub fn main() !u8 { |
| 141 | 148 | const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) { |
| 142 | 149 | error.TransformFail => { |
| 143 | 150 | std.debug.print("Transform failed:\n", .{}); |
| 144 | try fail_report.dumpToStdErr(&comp); | |
| 151 | try fail_report.dump(.initComp(&comp, main_file), comp.out); | |
| 145 | 152 | fail_report.deinit(comp.gpa); |
| 146 | 153 | return 1; |
| 147 | 154 | }, |
| ... | ... | @@ -157,8 +164,12 @@ pub fn main() !u8 { |
| 157 | 164 | try teg.dumpToWriter(stderr, null); |
| 158 | 165 | } |
| 159 | 166 | |
| 160 | const entry_point = comp.semaEntryPoint(main_file) catch |err| switch (err) { | |
| 161 | error.AnalysisFail => std.process.exit(1), | |
| 167 | const entry_point = try comp.enqueueEntryModule(main_file); | |
| 168 | comp.runAllJobs() catch |err| switch (err) { | |
| 169 | error.AnalysisFail => { | |
| 170 | // error is already printed | |
| 171 | return 1; | |
| 172 | }, | |
| 162 | 173 | else => return err, |
| 163 | 174 | }; |
| 164 | 175 | |
| ... | ... | @@ -229,7 +240,7 @@ const Comp = core.Comp; |
| 229 | 240 | const Report = core.Report; |
| 230 | 241 | |
| 231 | 242 | const tracer = @import("tracer"); |
| 232 | pub const tracer_impl = @field(tracer, @import("build_options").tracing); | |
| 243 | pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)); | |
| 233 | 244 | |
| 234 | 245 | const std = @import("std"); |
| 235 | 246 | const builtin = @import("builtin"); |
src/core/Air.Dump.zig+11-18| ... | ... | @@ -55,12 +55,6 @@ pub const State = struct { |
| 55 | 55 | const insts = index_list.slice(air); |
| 56 | 56 | try s.dumpInstList(w, insts, '{', '}'); |
| 57 | 57 | }, |
| 58 | .js_global_ref => { | |
| 59 | const name: InternPool.String.WithLen = air.instData(.js_global_ref, inst); | |
| 60 | try tty.setColor(w, .magenta); | |
| 61 | try w.print(" {s}", .{name.toSlice(s.ip)}); | |
| 62 | try tty.setColor(w, .reset); | |
| 63 | }, | |
| 64 | 58 | .arg => { |
| 65 | 59 | const arg: Air.Arg = air.instData(.arg, inst); |
| 66 | 60 | try w.writeAll(" "); |
| ... | ... | @@ -73,7 +67,7 @@ pub const State = struct { |
| 73 | 67 | .@"break" => { |
| 74 | 68 | const brk: Air.Break = air.instData(.@"break", inst); |
| 75 | 69 | try w.writeAll("("); |
| 76 | try s.dumpInstRef(w, brk.block_ref); | |
| 70 | try s.dumpInstRef(w, brk.block); | |
| 77 | 71 | try w.writeAll(", "); |
| 78 | 72 | try s.dumpRef(w, brk.value); |
| 79 | 73 | try w.writeAll(")"); |
| ... | ... | @@ -93,7 +87,8 @@ pub const State = struct { |
| 93 | 87 | .lt, |
| 94 | 88 | .gt, |
| 95 | 89 | .eq, |
| 96 | .index, | |
| 90 | .field_array, | |
| 91 | .field_opaque, | |
| 97 | 92 | => |comptime_tag| { |
| 98 | 93 | const bin: Air.Bin = air.instData(comptime_tag, inst); |
| 99 | 94 | try w.writeAll("("); |
| ... | ... | @@ -119,16 +114,6 @@ pub const State = struct { |
| 119 | 114 | try s.dumpValue(w, memory.ty.value()); |
| 120 | 115 | try w.writeAll(")"); |
| 121 | 116 | }, |
| 122 | .field_opaque => { | |
| 123 | const field: Air.FieldOpaque = air.instData(.field_opaque, inst); | |
| 124 | try w.writeAll("("); | |
| 125 | try s.dumpRef(w, field.base); | |
| 126 | try w.writeAll(", "); | |
| 127 | try tty.setColor(w, .magenta); | |
| 128 | try w.print("{s}", .{field.field.toSlice(s.ip)}); | |
| 129 | try tty.setColor(w, .reset); | |
| 130 | try w.writeAll(")"); | |
| 131 | }, | |
| 132 | 117 | .field_struct => { |
| 133 | 118 | const field: Air.FieldStruct = air.instData(.field_struct, inst); |
| 134 | 119 | try w.writeAll("("); |
| ... | ... | @@ -374,6 +359,14 @@ pub const State = struct { |
| 374 | 359 | try w.writeAll(">"); |
| 375 | 360 | try tty.setColor(w, .reset); |
| 376 | 361 | }, |
| 362 | .external_value => |external_value| { | |
| 363 | try w.writeAll("external '"); | |
| 364 | for (external_value.key, 0..) |part, i| { | |
| 365 | if (i != 0) try w.writeAll("."); | |
| 366 | try w.writeAll(part.toSlice(s.ip)); | |
| 367 | } | |
| 368 | try w.writeAll("'"); | |
| 369 | }, | |
| 377 | 370 | } |
| 378 | 371 | } |
| 379 | 372 |
src/core/Air.zig+14-15| ... | ... | @@ -14,7 +14,6 @@ pub const Inst = struct { |
| 14 | 14 | |
| 15 | 15 | pub const Tag = enum { |
| 16 | 16 | block, |
| 17 | js_global_ref, | |
| 18 | 17 | call, |
| 19 | 18 | js_new, |
| 20 | 19 | @"break", |
| ... | ... | @@ -29,11 +28,10 @@ pub const Inst = struct { |
| 29 | 28 | |
| 30 | 29 | field_opaque, |
| 31 | 30 | field_struct, |
| 31 | field_array, | |
| 32 | 32 | |
| 33 | 33 | array_len, |
| 34 | 34 | |
| 35 | index, | |
| 36 | ||
| 37 | 35 | // bin |
| 38 | 36 | add, |
| 39 | 37 | sub, |
| ... | ... | @@ -52,7 +50,6 @@ pub const Inst = struct { |
| 52 | 50 | pub fn Data(comptime tag: Tag) type { |
| 53 | 51 | return switch (tag) { |
| 54 | 52 | .block => IndexList, |
| 55 | .js_global_ref => InternPool.String.WithLen, | |
| 56 | 53 | .@"break" => Break, |
| 57 | 54 | .@"if" => If, |
| 58 | 55 | .call, .js_new => Call, |
| ... | ... | @@ -61,9 +58,9 @@ pub const Inst = struct { |
| 61 | 58 | .store => Store, |
| 62 | 59 | .struct_init => StructInit, |
| 63 | 60 | .array_init => ArrayInit, |
| 64 | .field_opaque => FieldOpaque, | |
| 65 | 61 | .field_struct => FieldStruct, |
| 66 | .index, | |
| 62 | .field_opaque, | |
| 63 | .field_array, | |
| 67 | 64 | .add, |
| 68 | 65 | .sub, |
| 69 | 66 | .mul, |
| ... | ... | @@ -216,7 +213,8 @@ pub const RefList = struct { |
| 216 | 213 | }; |
| 217 | 214 | |
| 218 | 215 | pub const Break = struct { |
| 219 | block_ref: Inst.Index, | |
| 216 | block: Inst.Index, | |
| 217 | // TODO: rename to operand | |
| 220 | 218 | value: Inst.Ref, |
| 221 | 219 | }; |
| 222 | 220 | |
| ... | ... | @@ -261,6 +259,7 @@ pub const If = struct { |
| 261 | 259 | pub const Data = struct { |
| 262 | 260 | yes: Inst.Ref, |
| 263 | 261 | no: Inst.Ref, |
| 262 | // TODO: delete this. both refs should be coerced to the common type | |
| 264 | 263 | union_ty: Type, |
| 265 | 264 | }; |
| 266 | 265 | }; |
| ... | ... | @@ -328,11 +327,11 @@ pub fn typeOf(air: *const Air, ref: Inst.Ref, ip: *const InternPool) InternPool. |
| 328 | 327 | pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) InternPool.Type { |
| 329 | 328 | return switch (air.instTag(index)) { |
| 330 | 329 | // These values are by definition, opaque |
| 331 | .js_global_ref, .js_new => .@"opaque", | |
| 330 | .js_new => .@"opaque", | |
| 332 | 331 | |
| 333 | 332 | // These operations always produce known types |
| 334 | 333 | .lt, .gt, .eq, .bool_not => .bool, |
| 335 | .@"break" => .noreturn, | |
| 334 | .@"break" => .never, | |
| 336 | 335 | .field_opaque => .@"opaque", |
| 337 | 336 | .array_len => .int32, |
| 338 | 337 | |
| ... | ... | @@ -353,7 +352,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In |
| 353 | 352 | .div, |
| 354 | 353 | => |t| { |
| 355 | 354 | const left = air.typeOf(air.instData(t, index).lhs, ip); |
| 356 | if (std.debug.runtime_safety) { | |
| 355 | if (core.runtime_safety) { | |
| 357 | 356 | assert(left == air.typeOf(air.instData(t, index).rhs, ip)); |
| 358 | 357 | } |
| 359 | 358 | return left; |
| ... | ... | @@ -362,7 +361,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In |
| 362 | 361 | .negate => air.typeOf(air.instData(.negate, index).operand, ip), |
| 363 | 362 | |
| 364 | 363 | // TODO: taking the last instruction is likely wrong. |
| 365 | // The last instruction is going to be a .break and be of type .noreturn | |
| 364 | // The last instruction is going to be a .break and be of type .never | |
| 366 | 365 | .block => { |
| 367 | 366 | const items = air.instData(.block, index).slice(air); |
| 368 | 367 | assert(items.len > 0); // empty blocks should always be `Value.void` |
| ... | ... | @@ -386,10 +385,10 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In |
| 386 | 385 | }, |
| 387 | 386 | |
| 388 | 387 | // Array accesses use the array they are accessing into. |
| 389 | .index => { | |
| 390 | const inst = air.instData(.index, index); | |
| 388 | .field_array => { | |
| 389 | const inst = air.instData(.field_array, index); | |
| 391 | 390 | const base_type = air.typeOf(inst.lhs, ip) |
| 392 | .toArrayType(ip) orelse unreachable; // index demands array type | |
| 391 | .toArrayType(ip) orelse unreachable; // field_array demands array type | |
| 393 | 392 | return base_type.item_type; |
| 394 | 393 | }, |
| 395 | 394 | }; |
| ... | ... | @@ -399,7 +398,7 @@ pub fn paramCount(air: Air) u32 { |
| 399 | 398 | for (air.insts.items(.tag)[1..], 0..) |tag, i| { |
| 400 | 399 | if (tag != .arg) return @intCast(i); |
| 401 | 400 | } |
| 402 | return @intCast(air.insts.len); | |
| 401 | return 0; | |
| 403 | 402 | } |
| 404 | 403 | |
| 405 | 404 | pub fn dumpToWriter(air: Air, ip: *InternPool, out: core.OutStream) !void { |
src/core/Ast.Dump.zig+13| ... | ... | @@ -188,6 +188,19 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err |
| 188 | 188 | try state.newline(writer); |
| 189 | 189 | try writer.writeAll(")"); |
| 190 | 190 | }, |
| 191 | .test_decl => { | |
| 192 | try writer.writeAll(" ("); | |
| 193 | if (data.test_decl.doc_test_name.unwrap()) |doc_test_name| { | |
| 194 | state.indent += 1; | |
| 195 | defer state.indent -= 1; | |
| 196 | try state.newline(writer); | |
| 197 | try dumpNode(writer, state, doc_test_name); | |
| 198 | try state.newline(writer); | |
| 199 | } | |
| 200 | try dumpNode(writer, state, data.bin.rhs); | |
| 201 | try state.newline(writer); | |
| 202 | try writer.writeAll(")"); | |
| 203 | }, | |
| 191 | 204 | .un => { |
| 192 | 205 | try writer.writeAll(" ("); |
| 193 | 206 | { |
src/core/Ast.zig+17-4| ... | ... | @@ -51,7 +51,6 @@ pub const Node = struct { |
| 51 | 51 | pub const Tag = enum { |
| 52 | 52 | /// Read with `ast.rootNodes()` |
| 53 | 53 | /// data is .list (containing stmt slice) |
| 54 | /// TODO: rename to `module` | |
| 55 | 54 | root, |
| 56 | 55 | /// main_token is the { |
| 57 | 56 | /// data is .list (containing stmt slice) |
| ... | ... | @@ -82,6 +81,8 @@ pub const Node = struct { |
| 82 | 81 | /// 'name: type' declaration |
| 83 | 82 | /// Main token is the identifier |
| 84 | 83 | param_decl, |
| 84 | /// | |
| 85 | test_decl, | |
| 85 | 86 | /// An identifier. main_token is the identifier. |
| 86 | 87 | id, |
| 87 | 88 | /// Main token is the data. May not fit in i32 range, |
| ... | ... | @@ -302,6 +303,7 @@ pub const Node = struct { |
| 302 | 303 | .bool_not = .un, |
| 303 | 304 | .builtin_call = .list, |
| 304 | 305 | .call = .call, |
| 306 | .test_decl = .test_decl, | |
| 305 | 307 | .div = .bin, |
| 306 | 308 | .div_assign = .bin, |
| 307 | 309 | .eq = .bin, |
| ... | ... | @@ -361,6 +363,10 @@ pub const Node = struct { |
| 361 | 363 | type: OptionalNodeIndex, |
| 362 | 364 | init: NodeIndex, |
| 363 | 365 | }, |
| 366 | test_decl: struct { | |
| 367 | doc_test_name: OptionalNodeIndex, | |
| 368 | block: NodeIndex, | |
| 369 | }, | |
| 364 | 370 | param_decl: struct { |
| 365 | 371 | type: OptionalNodeIndex, |
| 366 | 372 | unused: Unused = .zero, |
| ... | ... | @@ -588,7 +594,6 @@ pub fn fullFnDecl(ast: Ast, node: NodeIndex) full.FnDecl { |
| 588 | 594 | const public = tok_tags[main_token -| 1] == .kw_pub; |
| 589 | 595 | assert(tok_tags[main_token] == .kw_fn); |
| 590 | 596 | assert(tok_tags[main_token + 1] == .id); |
| 591 | assert(tok_tags[main_token + 2] == .start_paren); | |
| 592 | 597 | const id = ast.tokenSlice(main_token + 1); |
| 593 | 598 | |
| 594 | 599 | const data = ast.nodes.items(.data)[node].function; |
| ... | ... | @@ -653,7 +658,7 @@ pub fn fullVarDecl(ast: Ast, node: NodeIndex) full.VarDecl { |
| 653 | 658 | .kw_let => true, |
| 654 | 659 | .kw_var => false, |
| 655 | 660 | else => |tag| { |
| 656 | if (std.debug.runtime_safety) { | |
| 661 | if (core.runtime_safety) { | |
| 657 | 662 | std.debug.panic("Expected var decl, got {s}", .{@tagName(tag)}); |
| 658 | 663 | } |
| 659 | 664 | unreachable; |
| ... | ... | @@ -733,6 +738,14 @@ pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenInde |
| 733 | 738 | return Tokenizer.tokenLocation(self.utf8_source, start_offset, token_start); |
| 734 | 739 | } |
| 735 | 740 | |
| 741 | pub fn tokenTag(ast: Ast, token_index: TokenIndex) Token.Tag { | |
| 742 | return ast.tokens.items(.tag)[token_index]; | |
| 743 | } | |
| 744 | ||
| 745 | pub fn tokenOffset(ast: Ast, token_index: TokenIndex) u32 { | |
| 746 | return ast.tokens.items(.offset)[token_index]; | |
| 747 | } | |
| 748 | ||
| 736 | 749 | pub fn tokenSlice(ast: Ast, token_index: TokenIndex) []const u8 { |
| 737 | 750 | return ast.tokens.get(token_index).slice(ast.utf8_source); |
| 738 | 751 | } |
| ... | ... | @@ -805,7 +818,7 @@ const Report = core.Report; |
| 805 | 818 | /// based offsets, use this type to reduce mistakes. A common usage of this |
| 806 | 819 | /// would be line and column numbers for `Report.Item`s |
| 807 | 820 | pub fn Ordinal(Int: type) type { |
| 808 | comptime assert(@typeInfo(Int).Int.signedness == .unsigned); | |
| 821 | comptime assert(@typeInfo(Int).int.signedness == .unsigned); | |
| 809 | 822 | return enum(Int) { |
| 810 | 823 | start = 0, |
| 811 | 824 | _, |
src/core/Cache.zig+17| ... | ... | @@ -7,3 +7,20 @@ pub const BinDigest = [bin_digest_len]u8; |
| 7 | 7 | pub const HexDigest = [hex_digest_len]u8; |
| 8 | 8 | |
| 9 | 9 | pub const HashHelper = std.Build.Cache.HashHelper; |
| 10 | ||
| 11 | /// From Zig std.Build.Cache: | |
| 12 | /// The type used for hashing file contents. Currently, this is SipHash128(1, 3), because it | |
| 13 | /// provides enough collision resistance for the Manifest use cases, while being one of our | |
| 14 | /// fastest options right now. | |
| 15 | pub const Hasher = std.crypto.auth.siphash.SipHash128(1, 3); | |
| 16 | ||
| 17 | /// From Zig std.Build.Cache: | |
| 18 | /// Initial state with random bytes, that can be copied. | |
| 19 | /// Refresh this with new random bytes when the manifest | |
| 20 | /// format is modified in a non-backwards-compatible way. | |
| 21 | pub const hasher_init: Hasher = Hasher.init(&[_]u8{ | |
| 22 | 0x33, 0x52, 0xa2, 0x84, | |
| 23 | 0xcf, 0x17, 0x56, 0x57, | |
| 24 | 0x01, 0xbb, 0xcd, 0xe4, | |
| 25 | 0x77, 0xd6, 0xf0, 0x60, | |
| 26 | }); |
src/core/Comp.zig+227-117| ... | ... | @@ -3,72 +3,223 @@ |
| 3 | 3 | //! This is all of the state for a compilation, which starts at one or more URLs |
| 4 | 4 | //! (entry points), and will eventually emit one or more object files. |
| 5 | 5 | |
| 6 | /// General purpose allocator | |
| 6 | /// General purpose and thread-safe allocator. | |
| 7 | 7 | gpa: Allocator, |
| 8 | 8 | /// Stores all Type and Value objects. |
| 9 | intern_pool: InternPool = .{}, | |
| 10 | /// All files in the compilation | |
| 11 | /// TODO: turn this into a HashMap(*File, void) as File owns it's key | |
| 12 | files: std.StringHashMapUnmanaged(*File) = .{}, | |
| 13 | ||
| 9 | intern_pool: InternPool, | |
| 10 | /// Frontend options. | |
| 11 | options: Options, | |
| 12 | /// All text output is printed here. A typical compiler binary will set this to | |
| 13 | /// standard error, but a testing or web interface may want stronger control | |
| 14 | /// over the output. | |
| 14 | 15 | out: core.OutStream, |
| 16 | /// Backend and it's configuration. | |
| 17 | backend: Backend, | |
| 15 | 18 | |
| 16 | options: Options = .{}, | |
| 19 | /// All files in the compilation | |
| 20 | // TODO: turn this into a HashMap(*File, void) as File owns it's key | |
| 21 | files: std.StringHashMapUnmanaged(*File), | |
| 17 | 22 | |
| 18 | backend: Backend, | |
| 23 | ongoing_analysis: std.AutoHashMapUnmanaged(InternPool.AnalysisUnit, *Sema), | |
| 24 | ||
| 25 | jobs: LinearFifo(Job, .Dynamic), | |
| 19 | 26 | |
| 20 | 27 | // TODO: remove when it is possible to return `Report`s nicely |
| 21 | temp_flag_had_errors: bool = false, | |
| 28 | temp_flag_had_errors: bool, | |
| 29 | ||
| 30 | const WorkItem = union(enum) { | |
| 31 | sema: *Sema, | |
| 32 | code_gen: *Air, | |
| 33 | }; | |
| 34 | ||
| 35 | const InitOptions = struct { | |
| 36 | gpa: Allocator, | |
| 37 | options: Options = .{}, | |
| 38 | backend: Backend, | |
| 39 | out: ?core.OutStream = null, | |
| 40 | }; | |
| 41 | ||
| 42 | pub fn init(opts: InitOptions) !Comp { | |
| 43 | var comp: Comp = .{ | |
| 44 | .gpa = opts.gpa, | |
| 45 | .options = opts.options, | |
| 46 | .backend = opts.backend, | |
| 47 | .out = opts.out orelse .getStderr(), | |
| 48 | .files = .empty, | |
| 49 | .temp_flag_had_errors = false, | |
| 50 | .intern_pool = .{}, | |
| 51 | .jobs = .init(opts.gpa), | |
| 52 | .ongoing_analysis = .empty, | |
| 53 | }; | |
| 22 | 54 | |
| 23 | pub fn init(comp: *Comp) !void { | |
| 24 | 55 | try comp.intern_pool.init(comp.gpa); |
| 56 | ||
| 57 | return comp; | |
| 25 | 58 | } |
| 26 | 59 | |
| 27 | 60 | pub fn deinit(comp: *Comp) void { |
| 28 | comp.intern_pool.deinit(comp.gpa); | |
| 29 | var it = comp.files.valueIterator(); | |
| 30 | while (it.next()) |file| { | |
| 31 | file.*.deinit(comp.gpa); | |
| 61 | { | |
| 62 | var it = comp.ongoing_analysis.valueIterator(); | |
| 63 | while (it.next()) |sema| { | |
| 64 | _ = sema.*.deinit(.free_all); | |
| 65 | comp.gpa.destroy(sema.*); | |
| 66 | } | |
| 67 | comp.ongoing_analysis.deinit(comp.gpa); | |
| 32 | 68 | } |
| 33 | comp.files.deinit(comp.gpa); | |
| 69 | { | |
| 70 | var it = comp.files.valueIterator(); | |
| 71 | while (it.next()) |file| { | |
| 72 | file.*.deinit(comp.gpa); | |
| 73 | } | |
| 74 | comp.files.deinit(comp.gpa); | |
| 75 | } | |
| 76 | comp.intern_pool.deinit(comp.gpa); | |
| 34 | 77 | comp.backend.deinit(comp); |
| 78 | comp.jobs.deinit(); | |
| 35 | 79 | } |
| 36 | 80 | |
| 37 | /// This is the entry point for compilation. | |
| 38 | /// Returns the Decl of the top-level code. | |
| 39 | pub fn semaEntryPoint(comp: *Comp, file: *File) !Decl.Index { | |
| 81 | const Job = union(enum) { | |
| 82 | analysis: struct { | |
| 83 | au: InternPool.AnalysisUnit, | |
| 84 | first_blocked_sema: ?*Sema = null, | |
| 85 | }, | |
| 86 | codegen_func: struct { | |
| 87 | value: InternPool.Value, | |
| 88 | air: Air, | |
| 89 | }, | |
| 90 | ||
| 91 | pub fn dump(job: *const Job, ip: *const InternPool) std.fmt.Formatter(formatImpl) { | |
| 92 | return .{ .data = .{ .job = job, .ip = ip } }; | |
| 93 | } | |
| 94 | ||
| 95 | fn formatImpl( | |
| 96 | data: struct { job: *const Job, ip: *const InternPool }, | |
| 97 | comptime unused: []const u8, | |
| 98 | _: std.fmt.FormatOptions, | |
| 99 | w: std.io.AnyWriter, | |
| 100 | ) !void { | |
| 101 | comptime assert(unused.len == 0); | |
| 102 | try w.print(".{s}(", .{@tagName(data.job.*)}); | |
| 103 | switch (data.job.*) { | |
| 104 | .analysis => |detail| try w.print("{}", .{detail.au.dump(data.ip)}), | |
| 105 | .codegen_func => |detail| try w.print("{}", .{detail.value.dump(data.ip)}), | |
| 106 | } | |
| 107 | try w.writeAll(")"); | |
| 108 | } | |
| 109 | }; | |
| 110 | ||
| 111 | pub fn enqueueEntryModule(comp: *Comp, file: *File) !Decl.Index { | |
| 40 | 112 | var report: Report = undefined; |
| 41 | 113 | const teg = try file.getTeg(comp, &report); |
| 42 | 114 | |
| 43 | 115 | // TODO, move this elsewhere. we should print all errors and warnings at once |
| 44 | 116 | if (teg.report.warnings.len > 0) { |
| 45 | try teg.report.dumpToWriter(comp, comp.out.writer, comp.out.tty); | |
| 117 | try teg.report.dump(.initComp(comp, file), comp.out); | |
| 46 | 118 | } |
| 47 | 119 | |
| 48 | 120 | const ns = try comp.indexTegModule(file, .main_module_inst); |
| 49 | const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, .empty) orelse | |
| 50 | unreachable; // Transform should always emit a top-level function | |
| 51 | const decl = comp.intern_pool.declPtr(decl_index); | |
| 52 | ||
| 53 | var arena = std.heap.ArenaAllocator.init(comp.gpa); | |
| 54 | defer arena.deinit(); | |
| 55 | ||
| 56 | var sema: Sema = .{ | |
| 57 | .comp = comp, | |
| 58 | .gpa = comp.gpa, | |
| 59 | .code = teg, | |
| 60 | .arena = arena.allocator(), | |
| 61 | .ip = &comp.intern_pool, | |
| 62 | .decl = decl, | |
| 63 | .decl_index = decl_index, | |
| 64 | .namespace_index = ns, | |
| 121 | const main_str = try comp.intern_pool.getOrPutString(comp.gpa, "main", .no_embedded_nulls); | |
| 122 | const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, main_str) orelse | |
| 123 | @panic("missing 'fn main' in root module"); | |
| 124 | ||
| 125 | try comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .decl = decl_index }) } }); | |
| 126 | ||
| 127 | return decl_index; // root decl is needed for linking | |
| 128 | } | |
| 129 | ||
| 130 | pub fn runAllJobs(comp: *Comp) !void { | |
| 131 | while (comp.jobs.readItem()) |job| { | |
| 132 | // log.info("processing {}", .{job.dump(&comp.intern_pool)}); | |
| 133 | switch (job) { | |
| 134 | .analysis => |detail| try comp.runAnalysisJob(detail.au, detail.first_blocked_sema), | |
| 135 | .codegen_func => |detail| try comp.runCodegenJob(detail.value, detail.air), | |
| 136 | } | |
| 137 | } | |
| 138 | ||
| 139 | assert(comp.ongoing_analysis.size == 0); | |
| 140 | } | |
| 141 | ||
| 142 | const AnalysisJobError = Allocator.Error || error{ AnalysisFail, TooManyValues }; | |
| 143 | ||
| 144 | fn runAnalysisJob(comp: *Comp, unit: InternPool.AnalysisUnit, first_blocked_sema: ?*Sema) AnalysisJobError!void { | |
| 145 | const gop = try comp.ongoing_analysis.getOrPut(comp.gpa, unit); | |
| 146 | if (!gop.found_existing) { | |
| 147 | errdefer comp.ongoing_analysis.removeByPtr(gop.key_ptr); | |
| 148 | ||
| 149 | const sema = try comp.gpa.create(Sema); | |
| 150 | const decl_index = switch (unit.unwrap()) { | |
| 151 | .decl => |index| index, | |
| 152 | .func => |value| comp.intern_pool.valueToKey(value).func.owner_decl, | |
| 153 | }; | |
| 154 | const decl = comp.intern_pool.declPtr(decl_index); | |
| 155 | const file = comp.intern_pool.namespacePtr(decl.src_namespace).file_scope; | |
| 156 | const teg = file.teg orelse @panic("TODO: make sure teg is loaded to avoid annoying error set?"); | |
| 157 | ||
| 158 | try sema.init( | |
| 159 | comp, | |
| 160 | file, | |
| 161 | teg, | |
| 162 | unit, | |
| 163 | decl.src_namespace, | |
| 164 | std.heap.ArenaAllocator.init(comp.gpa), | |
| 165 | ); | |
| 166 | errdefer _ = sema.deinit(.free_all); | |
| 167 | gop.value_ptr.* = sema; | |
| 168 | ||
| 169 | if (first_blocked_sema) |blocked_sema| | |
| 170 | try sema.blocked_semas.append(comp.gpa, blocked_sema); | |
| 171 | } else { | |
| 172 | assert(first_blocked_sema == null); | |
| 173 | } | |
| 174 | ||
| 175 | const sema: *Sema = gop.value_ptr.*; | |
| 176 | sema.run() catch |err| switch (err) { | |
| 177 | error.Interrupt => return, | |
| 178 | ||
| 179 | error.OutOfMemory, | |
| 180 | error.TooManyValues, | |
| 181 | => |e| return e, | |
| 182 | ||
| 183 | error.AnalysisFail => |e| { | |
| 184 | const report = try sema.report.finalize(); | |
| 185 | defer report.deinit(comp.gpa); | |
| 186 | ||
| 187 | report.dump(.{ .comp = .{ .comp = comp, .file = sema.file } }, comp.out) catch {}; | |
| 188 | ||
| 189 | comp.ongoing_analysis.removeByPtr(gop.key_ptr); | |
| 190 | _ = sema.deinit(.free_all); // TODO: arena reuse | |
| 191 | comp.gpa.destroy(sema); | |
| 192 | ||
| 193 | return e; | |
| 194 | }, | |
| 65 | 195 | }; |
| 66 | try sema.init(); | |
| 67 | defer sema.deinit(); | |
| 68 | _ = try sema.analyzeDeclaration(); | |
| 69 | try sema.finalize(); | |
| 70 | 196 | |
| 71 | return decl_index; | |
| 197 | comp.ongoing_analysis.removeByPtr(gop.key_ptr); | |
| 198 | try comp.jobs.ensureUnusedCapacity(sema.blocked_semas.items.len); | |
| 199 | for (sema.blocked_semas.items) |other_sema| { | |
| 200 | comp.jobs.writeItemAssumeCapacity(.{ .analysis = .{ .au = other_sema.owner } }); | |
| 201 | } | |
| 202 | _ = sema.deinit(.free_all); // TODO: arena reuse | |
| 203 | comp.gpa.destroy(sema); | |
| 204 | } | |
| 205 | ||
| 206 | pub fn runCodegenJob(c: *Comp, value: InternPool.Value, air_const: Air) !void { | |
| 207 | const t = tracer.trace(@src()); | |
| 208 | defer t.end(); | |
| 209 | ||
| 210 | var arena_state = std.heap.ArenaAllocator.init(c.gpa); | |
| 211 | defer arena_state.deinit(); | |
| 212 | const arena = arena_state.allocator(); | |
| 213 | ||
| 214 | // TODO: put all this work on another thread | |
| 215 | if (c.options.dump_ir) { | |
| 216 | air_const.dumpToWriter(&c.intern_pool, c.out) catch {}; | |
| 217 | } | |
| 218 | ||
| 219 | var air = air_const; // this line is a workaround | |
| 220 | defer air.deinit(c.gpa); | |
| 221 | ||
| 222 | _ = try c.backend.handleFunctionAir(c, value, air, arena); | |
| 72 | 223 | } |
| 73 | 224 | |
| 74 | 225 | /// Add or retrieve a file to the compilation via it's file path. |
| ... | ... | @@ -83,7 +234,6 @@ pub fn getFile(comp: *Comp, absolute_file_path: []const u8) !*File { |
| 83 | 234 | .file_path = absolute_file_path, |
| 84 | 235 | .path_digest = null, |
| 85 | 236 | .root_namespace = .none, |
| 86 | .status = .never_loaded, | |
| 87 | 237 | .source = null, |
| 88 | 238 | .ast = null, |
| 89 | 239 | .teg = null, |
| ... | ... | @@ -104,17 +254,6 @@ pub const File = struct { |
| 104 | 254 | file_path: []const u8, |
| 105 | 255 | |
| 106 | 256 | root_namespace: Namespace.Index.Optional, |
| 107 | status: enum { | |
| 108 | never_loaded, | |
| 109 | /// A failure occurred while loading the file. Retrying might succeed. | |
| 110 | retryable_failure, | |
| 111 | /// Errors during parse | |
| 112 | parse_failure, | |
| 113 | /// Errors during transform | |
| 114 | transform_failure, | |
| 115 | /// Teg is valid | |
| 116 | success, | |
| 117 | }, | |
| 118 | 257 | |
| 119 | 258 | source: ?Source, |
| 120 | 259 | ast: ?Ast, |
| ... | ... | @@ -170,8 +309,8 @@ pub const File = struct { |
| 170 | 309 | |
| 171 | 310 | pub const GetAstError = GetSourceError || Parse.Error; |
| 172 | 311 | |
| 173 | pub fn getAst(file: *File, comp: *Comp) GetAstError!Ast { | |
| 174 | if (file.ast) |ast| { | |
| 312 | pub fn getAst(file: *File, comp: *Comp) GetAstError!*const Ast { | |
| 313 | if (file.ast) |*ast| { | |
| 175 | 314 | return ast; |
| 176 | 315 | } |
| 177 | 316 | |
| ... | ... | @@ -184,9 +323,9 @@ pub const File = struct { |
| 184 | 323 | error.OutOfMemory => return err, |
| 185 | 324 | error.ParseFail => { |
| 186 | 325 | // TODO: let this merge with other report |
| 187 | var report = try err_data.toReport(comp, file); | |
| 188 | report.dumpToWriter(comp, comp.out.writer, comp.out.tty) catch {}; | |
| 189 | report.deinit(comp.gpa); | |
| 326 | var report = try err_data.toReport(comp.gpa); | |
| 327 | defer report.deinit(comp.gpa); | |
| 328 | report.dump(.{ .comp = .{ .comp = comp, .file = file } }, comp.out) catch {}; | |
| 190 | 329 | return err; |
| 191 | 330 | }, |
| 192 | 331 | }; |
| ... | ... | @@ -198,7 +337,7 @@ pub const File = struct { |
| 198 | 337 | } |
| 199 | 338 | |
| 200 | 339 | file.ast = ast; |
| 201 | return ast; | |
| 340 | return &(file.ast.?); | |
| 202 | 341 | } |
| 203 | 342 | |
| 204 | 343 | pub const GetTegError = GetAstError || Transform.Error; |
| ... | ... | @@ -210,7 +349,7 @@ pub const File = struct { |
| 210 | 349 | |
| 211 | 350 | const bin_digest = hash: { |
| 212 | 351 | var path_hash: Cache.HashHelper = .{}; |
| 213 | path_hash.addBytes("0.1.0"); | |
| 352 | path_hash.addBytes(@import("config").version_string); | |
| 214 | 353 | path_hash.addBytes(file.file_path); |
| 215 | 354 | var bin: Cache.BinDigest = undefined; |
| 216 | 355 | path_hash.hasher.final(&bin); |
| ... | ... | @@ -218,12 +357,7 @@ pub const File = struct { |
| 218 | 357 | }; |
| 219 | 358 | file.path_digest = bin_digest; |
| 220 | 359 | |
| 221 | const teg = try Transform.transform( | |
| 222 | try file.getAst(comp), | |
| 223 | comp.gpa, | |
| 224 | file, | |
| 225 | fail_report, | |
| 226 | ); | |
| 360 | const teg = try Transform.transform((try file.getAst(comp)).*, comp.gpa, fail_report); | |
| 227 | 361 | |
| 228 | 362 | file.teg = teg; |
| 229 | 363 | return teg; |
| ... | ... | @@ -274,8 +408,6 @@ pub const File = struct { |
| 274 | 408 | jw.write(std.fmt.bytesToHex(digest, .lower)) |
| 275 | 409 | else |
| 276 | 410 | jw.write(null); |
| 277 | try jw.objectField("status"); | |
| 278 | try jw.write(ctx.file.status); | |
| 279 | 411 | try jw.objectField("root_namespace"); |
| 280 | 412 | if (ctx.file.root_namespace.unwrap()) |ns_index| { |
| 281 | 413 | const ns = ctx.comp.intern_pool.namespacePtrConst(ns_index); |
| ... | ... | @@ -325,29 +457,8 @@ pub const Decl = struct { |
| 325 | 457 | /// This Decl corresponds to an AST Node that has not been referenced yet, and therefore |
| 326 | 458 | /// because of Zig's lazy declaration analysis, it will remain unanalyzed until referenced. |
| 327 | 459 | unreferenced, |
| 328 | /// Semantic analysis for this Decl is running right now. | |
| 329 | /// This state detects dependency loops. | |
| 330 | 460 | in_progress, |
| 331 | /// Semantic analysis for this Decl is running right now, and has | |
| 332 | /// completed enough to reify the type of the result, but not the result | |
| 333 | /// itself. | |
| 334 | in_progress_type_available, | |
| 335 | /// The file corresponding to this Decl had a parse error or ZIR error. | |
| 336 | /// There will be a corresponding ErrorMsg in Zcu.failed_files. | |
| 337 | // file_failure, | |
| 338 | /// This Decl might be OK but it depends on another one which did not | |
| 339 | /// successfully complete semantic analysis. | |
| 340 | // dependency_failure, | |
| 341 | /// Semantic analysis failure. | |
| 342 | /// There will be a corresponding ErrorMsg in Zcu.failed_decls. | |
| 343 | // sema_failure, | |
| 344 | /// There will be a corresponding ErrorMsg in Zcu.failed_decls. | |
| 345 | // codegen_failure, | |
| 346 | /// Semantic analysis and constant value codegen of this Decl has | |
| 347 | /// succeeded. However, the Decl may be outdated due to an in-progress | |
| 348 | /// update. Note that for a function, this does not mean codegen of the | |
| 349 | /// function body succeeded: that state is indicated by the function's | |
| 350 | /// `analysis` field. | |
| 461 | waiting, | |
| 351 | 462 | complete, |
| 352 | 463 | }, |
| 353 | 464 | |
| ... | ... | @@ -513,38 +624,37 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N |
| 513 | 624 | errdefer comp.intern_pool.destroyNamespace(comp.gpa, ns); |
| 514 | 625 | |
| 515 | 626 | for (teg.moduleDeclarations(module_index)) |decl_index| { |
| 516 | const name = try comp.intern_pool.getOrPutString( | |
| 517 | comp.gpa, | |
| 518 | teg.declName(decl_index), | |
| 519 | .no_embedded_nulls, | |
| 520 | ); | |
| 521 | const tracked_inst = try comp.intern_pool.trackInst(comp.gpa, file, decl_index); | |
| 522 | const decl = try comp.intern_pool.createDecl(comp.gpa, .{ | |
| 523 | .name = name, | |
| 524 | .raw_value = null, | |
| 525 | .src_namespace = ns, | |
| 526 | .teg_decl_index = tracked_inst, | |
| 527 | .analysis = .unreferenced, | |
| 528 | }); | |
| 529 | try ns_ptr.decls.putNoClobberContext(comp.gpa, decl, void{}, .{ .comp = comp }); | |
| 627 | switch (teg.instTag(decl_index)) { | |
| 628 | .declaration => { | |
| 629 | const name = try comp.intern_pool.getOrPutString( | |
| 630 | comp.gpa, | |
| 631 | teg.declName(decl_index), | |
| 632 | .no_embedded_nulls, | |
| 633 | ); | |
| 634 | const tracked_inst = try comp.intern_pool.trackInst(comp.gpa, file, decl_index); | |
| 635 | const decl = try comp.intern_pool.createDecl(comp.gpa, .{ | |
| 636 | .name = name, | |
| 637 | .raw_value = null, | |
| 638 | .src_namespace = ns, | |
| 639 | .teg_decl_index = tracked_inst, | |
| 640 | .analysis = .unreferenced, | |
| 641 | }); | |
| 642 | try ns_ptr.decls.putNoClobberContext(comp.gpa, decl, void{}, .{ .comp = comp }); | |
| 643 | }, | |
| 644 | .variable_declaration => @panic("TODO: variable declarations"), | |
| 645 | .test_declaration => { | |
| 646 | // TODO: test mode | |
| 647 | }, | |
| 648 | else => unreachable, | |
| 649 | } | |
| 530 | 650 | } |
| 531 | 651 | |
| 532 | 652 | return ns; |
| 533 | 653 | } |
| 534 | 654 | |
| 535 | pub fn codeGenDecl(c: *Comp, value: InternPool.Value, air: *const Air) !void { | |
| 536 | const t = tracer.trace(@src()); | |
| 537 | defer t.end(); | |
| 538 | ||
| 539 | var arena_state = std.heap.ArenaAllocator.init(c.gpa); | |
| 540 | defer arena_state.deinit(); | |
| 541 | const arena = arena_state.allocator(); | |
| 542 | ||
| 543 | _ = try c.backend.handleFunctionAir(c, value, air, arena); | |
| 544 | } | |
| 545 | ||
| 546 | 655 | const Options = struct { |
| 547 | 656 | dump_ir: bool = false, |
| 657 | verbose_sema: bool = false, | |
| 548 | 658 | }; |
| 549 | 659 | |
| 550 | 660 | /// The compiler backend is stored separately from the compilation, and |
| ... | ... | @@ -565,7 +675,7 @@ pub const Backend = union(enum) { |
| 565 | 675 | backend: Backend, |
| 566 | 676 | comp: *Comp, |
| 567 | 677 | value: InternPool.Value, |
| 568 | air: *const Air, | |
| 678 | air: Air, | |
| 569 | 679 | arena: Allocator, |
| 570 | 680 | ) HandleFunctionAirError!u32 { |
| 571 | 681 | return backend.dispatch("handleFunctionAir", .{ comp, value, air, arena }); |
| ... | ... | @@ -575,7 +685,7 @@ pub const Backend = union(enum) { |
| 575 | 685 | backend: Backend, |
| 576 | 686 | comptime name: []const u8, |
| 577 | 687 | args: anytype, |
| 578 | ) @typeInfo(@TypeOf(@field(Backend, name))).Fn.return_type.? { | |
| 688 | ) @typeInfo(@TypeOf(@field(Backend, name))).@"fn".return_type.? { | |
| 579 | 689 | return switch (backend) { |
| 580 | 690 | inline else => |ptr| @call(.auto, @field(@TypeOf(ptr.*), name), .{ptr} ++ args), |
| 581 | 691 | }; |
| ... | ... | @@ -607,7 +717,7 @@ const std = @import("std"); |
| 607 | 717 | const Allocator = std.mem.Allocator; |
| 608 | 718 | const assert = std.debug.assert; |
| 609 | 719 | const Stat = std.Build.Cache.File.Stat; |
| 610 | const log = std.log.scoped(.comp); | |
| 720 | const LinearFifo = std.fifo.LinearFifo; | |
| 611 | 721 | |
| 612 | 722 | const core = @import("mod.zig"); |
| 613 | 723 | const InternPool = core.InternPool; |
src/core/InternPool.zig+254-84| ... | ... | @@ -8,6 +8,11 @@ |
| 8 | 8 | //! |
| 9 | 9 | //! This data structure is self-contained, with the following exceptions: |
| 10 | 10 | //! * Comp.Namespace has a pointer to Comp.File |
| 11 | //! | |
| 12 | //! TODO: This structure does not really consider threads and concurrent | |
| 13 | //! access. Zig 0.14 has some interesting work that I have not yet studied: | |
| 14 | //! https://github.com/ziglang/zig/blob/master/src/InternPool.zig | |
| 15 | //! This file will likely to be rewritten to inherit Zig's improvements. | |
| 11 | 16 | |
| 12 | 17 | /// Maps `Key` to `Value`. `Key` objects are not stored anywhere; they are |
| 13 | 18 | /// constructed lazily. The `Value` is also stored out of line in `.items`. The |
| ... | ... | @@ -25,7 +30,7 @@ extra: ExtraStore = .{}, |
| 25 | 30 | /// is referencing the data here whether they want to store both index and length, |
| 26 | 31 | /// thus allowing null bytes, or store only index, and use null-termination. The |
| 27 | 32 | /// `string_bytes` array is agnostic to either usage. |
| 28 | string_bytes: std.ArrayListUnmanaged(u8) = .{}, | |
| 33 | string_bytes: std.ArrayListUnmanaged(u8) = .empty, | |
| 29 | 34 | |
| 30 | 35 | /// Used for finding the index inside `string_bytes`. |
| 31 | 36 | string_table: std.HashMapUnmanaged( |
| ... | ... | @@ -33,11 +38,11 @@ string_table: std.HashMapUnmanaged( |
| 33 | 38 | void, |
| 34 | 39 | std.hash_map.StringIndexContext, |
| 35 | 40 | std.hash_map.default_max_load_percentage, |
| 36 | ) = .{}, | |
| 41 | ) = .empty, | |
| 37 | 42 | |
| 38 | 43 | /// An index into `tracked_insts` gives a reference to a single ZIR instruction which |
| 39 | 44 | /// persists across incremental updates. |
| 40 | tracked_insts: std.AutoArrayHashMapUnmanaged(TrackedInst, void) = .{}, | |
| 45 | tracked_insts: std.AutoArrayHashMapUnmanaged(TrackedInst, void) = .empty, | |
| 41 | 46 | |
| 42 | 47 | /// Rather than allocating Decl objects with an Allocator, we instead allocate |
| 43 | 48 | /// them with this SegmentedList. This provides four advantages: |
| ... | ... | @@ -58,6 +63,31 @@ allocated_namespaces: std.SegmentedList(Comp.Namespace, 0) = .{}, |
| 58 | 63 | /// Same pattern as with `decls_free_list`. |
| 59 | 64 | namespaces_free_list: std.ArrayListUnmanaged(NamespaceIndex) = .{}, |
| 60 | 65 | |
| 66 | /// Dependencies on the source code hash associated with a ZIR instruction. | |
| 67 | /// * For a `declaration`, this is the entire declaration body. | |
| 68 | /// * For a `struct_decl`, `union_decl`, etc, this is the source of the fields (but not declarations). | |
| 69 | /// * For a `func`, this is the source of the full function signature. | |
| 70 | /// These are also invalidated if tracking fails for this instruction. | |
| 71 | /// Value is index into `dep_entries` of the first dependency on this hash. | |
| 72 | src_hash_deps: std.AutoArrayHashMapUnmanaged(TrackedInst.Index, DepEntry.Index) = .{}, | |
| 73 | /// Dependencies on the value of a Decl. | |
| 74 | /// Value is index into `dep_entries` of the first dependency on this Decl value. | |
| 75 | decl_val_deps: std.AutoArrayHashMapUnmanaged(DeclIndex, DepEntry.Index) = .{}, | |
| 76 | ||
| 77 | /// Given a `Depender`, points to an entry in `dep_entries` whose `depender` | |
| 78 | /// matches. The `next_dependee` field can be used to iterate all such entries | |
| 79 | /// and remove them from the corresponding lists. | |
| 80 | first_dependency: std.AutoArrayHashMapUnmanaged(AnalysisUnit, DepEntry.Index) = .{}, | |
| 81 | ||
| 82 | /// Stores dependency information. The hashmaps declared above are used to look | |
| 83 | /// up entries in this list as required. This is not stored in `extra` so that | |
| 84 | /// we can use `free_dep_entries` to track free indices, since dependencies are | |
| 85 | /// removed frequently. | |
| 86 | dep_entries: std.ArrayListUnmanaged(DepEntry) = .{}, | |
| 87 | /// Stores unused indices in `dep_entries` which can be reused without a full | |
| 88 | /// garbage collection pass. | |
| 89 | free_dep_entries: std.ArrayListUnmanaged(DepEntry.Index) = .{}, | |
| 90 | ||
| 61 | 91 | pub const Tag = enum(u8) { |
| 62 | 92 | /// This special tag represents a value that was removed from this pool via |
| 63 | 93 | /// `InternPool.remove`. The item remains allocated to preserve indices, but |
| ... | ... | @@ -91,15 +121,16 @@ pub const Tag = enum(u8) { |
| 91 | 121 | func, |
| 92 | 122 | /// A string |
| 93 | 123 | string, |
| 94 | ||
| 124 | /// An instance of a struct | |
| 95 | 125 | struct_value, |
| 96 | ||
| 126 | /// An instance of an array | |
| 97 | 127 | array_value, |
| 128 | /// An extern reference. For JS, this can be a property access | |
| 129 | external_value, | |
| 98 | 130 | |
| 99 | 131 | pub fn Data(comptime tag: Tag) type { |
| 100 | 132 | return switch (tag) { |
| 101 | .removed, | |
| 102 | => unreachable, // does not have a data | |
| 133 | .removed => unreachable, // does not have a data | |
| 103 | 134 | |
| 104 | 135 | .simple_type => SimpleType, |
| 105 | 136 | .simple_value => SimpleValue, |
| ... | ... | @@ -125,6 +156,7 @@ pub const Tag = enum(u8) { |
| 125 | 156 | .struct_type => StructType, |
| 126 | 157 | .array_type => ArrayType, |
| 127 | 158 | .array_value => ArrayValue, |
| 159 | .external_value => ExternalValue, | |
| 128 | 160 | }; |
| 129 | 161 | } |
| 130 | 162 | |
| ... | ... | @@ -137,15 +169,15 @@ pub const Tag = enum(u8) { |
| 137 | 169 | }; |
| 138 | 170 | |
| 139 | 171 | /// Trailing: |
| 140 | /// 0. param_type: Index for each params_len | |
| 172 | /// 0. param_type: Index to param type. Repeated .params_len | |
| 141 | 173 | pub const TypeFunction = struct { |
| 142 | 174 | params_len: u32, |
| 143 | 175 | return_type: Type, |
| 144 | 176 | }; |
| 145 | 177 | |
| 146 | 178 | /// Trailing: |
| 147 | /// 0. field_key: NullTerminatedString. Repeated per field_len | |
| 148 | /// 1. field_type: Type. Repeated per field_len | |
| 179 | /// 0. field_key: NullTerminatedString. Repeated .field_len | |
| 180 | /// 1. field_type: Type. Repeated .field_len | |
| 149 | 181 | pub const StructType = struct { |
| 150 | 182 | fields_len: u32, |
| 151 | 183 | }; |
| ... | ... | @@ -156,7 +188,7 @@ pub const Tag = enum(u8) { |
| 156 | 188 | }; |
| 157 | 189 | |
| 158 | 190 | /// Trailing: |
| 159 | /// 1. item: Type. Repeated per items_len | |
| 191 | /// 1. item: Type. Repeated per .items_len | |
| 160 | 192 | pub const ArrayValue = struct { |
| 161 | 193 | /// This is the array type, not the item type |
| 162 | 194 | type: Type, |
| ... | ... | @@ -164,11 +196,17 @@ pub const Tag = enum(u8) { |
| 164 | 196 | }; |
| 165 | 197 | |
| 166 | 198 | /// Trailing: |
| 167 | /// 0. value: Value. Repeated ty.structInfo().field_len | |
| 199 | /// 0. value: Value. Repeated .ty.structInfo().field_len | |
| 168 | 200 | pub const StructValue = struct { |
| 169 | 201 | /// The type of this field |
| 170 | 202 | ty: Type, |
| 171 | 203 | }; |
| 204 | ||
| 205 | /// Trailing: | |
| 206 | /// 0. value: Value. Repeated .key_count | |
| 207 | pub const ExternalValue = struct { | |
| 208 | key_count: u32, | |
| 209 | }; | |
| 172 | 210 | }; |
| 173 | 211 | |
| 174 | 212 | /// This is an index into .items |
| ... | ... | @@ -183,11 +221,11 @@ pub const Value = enum(u32) { |
| 183 | 221 | int32_type, |
| 184 | 222 | float_type, |
| 185 | 223 | string_type, |
| 186 | noreturn_type, | |
| 224 | never_type, | |
| 187 | 225 | type_type, |
| 188 | 226 | numeric_literal_type, |
| 189 | 227 | numeric_literal_array_type, |
| 190 | noreturn_array_type, | |
| 228 | never_array_type, | |
| 191 | 229 | |
| 192 | 230 | void, |
| 193 | 231 | bool_true, |
| ... | ... | @@ -289,6 +327,7 @@ pub const Value = enum(u32) { |
| 289 | 327 | .func => value.toKey(ip).func.ty, |
| 290 | 328 | .struct_value => value.toKey(ip).struct_value.ty, |
| 291 | 329 | .array_value => value.toKey(ip).array_value.type, |
| 330 | .external_value => .@"opaque", | |
| 292 | 331 | }; |
| 293 | 332 | } |
| 294 | 333 | |
| ... | ... | @@ -312,7 +351,7 @@ pub const Value = enum(u32) { |
| 312 | 351 | } |
| 313 | 352 | |
| 314 | 353 | pub fn toTypeUnsafe(value: Value, proof_ip: *const InternPool) Type { |
| 315 | if (std.debug.runtime_safety) { | |
| 354 | if (core.runtime_safety) { | |
| 316 | 355 | return value.toType(proof_ip) orelse |
| 317 | 356 | std.debug.panic("{} is not a type", .{value.dump(proof_ip)}); |
| 318 | 357 | } |
| ... | ... | @@ -347,11 +386,11 @@ pub const Type = enum(u32) { |
| 347 | 386 | int32 = @intFromEnum(Value.int32_type), |
| 348 | 387 | float = @intFromEnum(Value.float_type), |
| 349 | 388 | string = @intFromEnum(Value.string_type), |
| 350 | noreturn = @intFromEnum(Value.noreturn_type), | |
| 389 | never = @intFromEnum(Value.never_type), | |
| 351 | 390 | type = @intFromEnum(Value.type_type), |
| 352 | 391 | numeric_literal = @intFromEnum(Value.numeric_literal_type), |
| 353 | 392 | numeric_literal_array = @intFromEnum(Value.numeric_literal_array_type), |
| 354 | noreturn_array = @intFromEnum(Value.noreturn_array_type), | |
| 393 | never_array = @intFromEnum(Value.never_array_type), | |
| 355 | 394 | void = @intFromEnum(Value.void), |
| 356 | 395 | _, |
| 357 | 396 | |
| ... | ... | @@ -416,7 +455,7 @@ pub const Type = enum(u32) { |
| 416 | 455 | => true, |
| 417 | 456 | .numeric_literal, |
| 418 | 457 | .type, |
| 419 | .noreturn, | |
| 458 | .never, | |
| 420 | 459 | => false, |
| 421 | 460 | else => switch (ty.tag(ip)) { |
| 422 | 461 | .simple_type => unreachable, // all simple types are already printed |
| ... | ... | @@ -441,7 +480,7 @@ pub const Type = enum(u32) { |
| 441 | 480 | .void, |
| 442 | 481 | .numeric_literal, |
| 443 | 482 | .type, |
| 444 | .noreturn, | |
| 483 | .never, | |
| 445 | 484 | => false, |
| 446 | 485 | else => switch (ty.tag(ip)) { |
| 447 | 486 | .simple_type => unreachable, // all simple types are already printed |
| ... | ... | @@ -476,7 +515,7 @@ pub const Type = enum(u32) { |
| 476 | 515 | .int32 => return try writer.writeAll("int"), |
| 477 | 516 | .float => return try writer.writeAll("float"), |
| 478 | 517 | .string => return try writer.writeAll("string"), |
| 479 | .noreturn => return try writer.writeAll("noreturn"), | |
| 518 | .never => return try writer.writeAll("never"), | |
| 480 | 519 | .type => return try writer.writeAll("type"), |
| 481 | 520 | .numeric_literal => return try writer.writeAll("numeric literal"), |
| 482 | 521 | .void => return try writer.writeAll("void"), |
| ... | ... | @@ -512,7 +551,7 @@ pub const Item = struct { |
| 512 | 551 | data: EncodedData, |
| 513 | 552 | |
| 514 | 553 | pub fn encode(comptime tag: Tag, data: tag.Data()) Item { |
| 515 | return .{ .tag = tag, .data = EncodedData.encode(tag, data) }; | |
| 554 | return .{ .tag = tag, .data = .encode(tag, data) }; | |
| 516 | 555 | } |
| 517 | 556 | }; |
| 518 | 557 | |
| ... | ... | @@ -536,11 +575,11 @@ pub const static_keys = [_]Key{ |
| 536 | 575 | SimpleType.int32.key(), |
| 537 | 576 | SimpleType.float.key(), |
| 538 | 577 | SimpleType.string.key(), |
| 539 | SimpleType.noreturn.key(), | |
| 578 | SimpleType.never.key(), | |
| 540 | 579 | SimpleType.type.key(), |
| 541 | 580 | SimpleType.numeric_literal.key(), |
| 542 | 581 | .{ .array_type = .{ .item_type = Type.numeric_literal } }, |
| 543 | .{ .array_type = .{ .item_type = Type.noreturn } }, | |
| 582 | .{ .array_type = .{ .item_type = Type.never } }, | |
| 544 | 583 | |
| 545 | 584 | SimpleValue.void.key(), |
| 546 | 585 | SimpleValue.true.key(), |
| ... | ... | @@ -549,7 +588,7 @@ pub const static_keys = [_]Key{ |
| 549 | 588 | .{ .float = 0.0 }, |
| 550 | 589 | .{ .float = Float.pure_nan.decode() }, |
| 551 | 590 | .{ .array_value = .{ |
| 552 | .type = Type.noreturn_array, | |
| 591 | .type = Type.never_array, | |
| 553 | 592 | .items = &.{}, |
| 554 | 593 | } }, |
| 555 | 594 | .{ .string = "" }, |
| ... | ... | @@ -646,10 +685,6 @@ pub const NullTerminatedString = enum(u32) { |
| 646 | 685 | return @enumFromInt(@intFromEnum(self)); |
| 647 | 686 | } |
| 648 | 687 | |
| 649 | // pub fn toOptional(self: NullTerminatedString) Optional{ | |
| 650 | // return @enumFromInt(@intFromEnum(self)); | |
| 651 | // } | |
| 652 | ||
| 653 | 688 | pub fn toSlice(string: NullTerminatedString, ip: *const InternPool) [:0]const u8 { |
| 654 | 689 | const slice = ip.string_bytes.items[@intFromEnum(string)..]; |
| 655 | 690 | return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0]; |
| ... | ... | @@ -801,51 +836,6 @@ pub const NamespaceIndex = enum(u32) { |
| 801 | 836 | }; |
| 802 | 837 | }; |
| 803 | 838 | |
| 804 | /// A single value captured in the closure of a namespace type. This is not a plain | |
| 805 | /// `Index` because we must differentiate between the following cases: | |
| 806 | /// * runtime-known value (where we store the type) | |
| 807 | /// * comptime-known value (where we store the value) | |
| 808 | /// * decl val (so that we can analyze the value lazily) | |
| 809 | /// * decl ref (so that we can analyze the reference lazily) | |
| 810 | pub const Capture = packed struct(u32) { | |
| 811 | tag: enum(u2) { @"comptime", runtime, decl_val, decl_ref }, | |
| 812 | idx: u30, | |
| 813 | ||
| 814 | pub fn wrap(val: Unwrapped) Capture { | |
| 815 | return switch (val) { | |
| 816 | .@"comptime" => |i| .{ .tag = .@"comptime", .idx = @intCast(@intFromEnum(i)) }, | |
| 817 | .runtime => |i| .{ .tag = .runtime, .idx = @intCast(@intFromEnum(i)) }, | |
| 818 | .decl_val => |i| .{ .tag = .decl_val, .idx = @intCast(@intFromEnum(i)) }, | |
| 819 | .decl_ref => |i| .{ .tag = .decl_ref, .idx = @intCast(@intFromEnum(i)) }, | |
| 820 | }; | |
| 821 | } | |
| 822 | pub fn unwrap(val: Capture) Unwrapped { | |
| 823 | return switch (val.tag) { | |
| 824 | .@"comptime" => .{ .@"comptime" = @enumFromInt(val.idx) }, | |
| 825 | .runtime => .{ .runtime = @enumFromInt(val.idx) }, | |
| 826 | .decl_val => .{ .decl_val = @enumFromInt(val.idx) }, | |
| 827 | .decl_ref => .{ .decl_ref = @enumFromInt(val.idx) }, | |
| 828 | }; | |
| 829 | } | |
| 830 | ||
| 831 | pub const Unwrapped = union(enum) { | |
| 832 | /// Index refers to the value. | |
| 833 | @"comptime": Value, | |
| 834 | /// Index refers to the type. | |
| 835 | runtime: Value, | |
| 836 | decl_val: DeclIndex, | |
| 837 | decl_ref: DeclIndex, | |
| 838 | }; | |
| 839 | ||
| 840 | pub const Slice = struct { | |
| 841 | start: u32, | |
| 842 | len: u32, | |
| 843 | pub fn get(slice: Slice, ip: *const InternPool) []Capture { | |
| 844 | return @ptrCast(ip.extra.list.items[slice.start..][0..slice.len]); | |
| 845 | } | |
| 846 | }; | |
| 847 | }; | |
| 848 | ||
| 849 | 839 | const SimpleValue = enum { |
| 850 | 840 | true, |
| 851 | 841 | false, |
| ... | ... | @@ -862,7 +852,7 @@ const SimpleType = enum { |
| 862 | 852 | float, |
| 863 | 853 | string, |
| 864 | 854 | bool, |
| 865 | noreturn, | |
| 855 | never, | |
| 866 | 856 | type, |
| 867 | 857 | numeric_literal, |
| 868 | 858 | |
| ... | ... | @@ -886,6 +876,7 @@ pub const Key = union(enum) { |
| 886 | 876 | function_type: FunctionType, |
| 887 | 877 | struct_type: StructType, |
| 888 | 878 | array_type: ArrayType, |
| 879 | external_value: ExternalValue, | |
| 889 | 880 | |
| 890 | 881 | const Adapter = struct { |
| 891 | 882 | intern_pool: *const InternPool, |
| ... | ... | @@ -950,6 +941,16 @@ pub const Key = union(enum) { |
| 950 | 941 | } |
| 951 | 942 | }; |
| 952 | 943 | |
| 944 | pub const ExternalValue = struct { | |
| 945 | /// A list of strings to generate the property access | |
| 946 | /// so `console.log` would be .{ "console", "log" } | |
| 947 | key: []const NullTerminatedString, | |
| 948 | ||
| 949 | pub fn hash(self: ExternalValue, hasher: *Hash) void { | |
| 950 | std.hash.autoHashStrat(hasher, self.key, .Shallow); | |
| 951 | } | |
| 952 | }; | |
| 953 | ||
| 953 | 954 | pub const ArrayValue = struct { |
| 954 | 955 | /// This is the array type, not the item type |
| 955 | 956 | type: Type, |
| ... | ... | @@ -983,7 +984,7 @@ pub const Key = union(enum) { |
| 983 | 984 | defer t.end(); |
| 984 | 985 | |
| 985 | 986 | const asBytes = std.mem.asBytes; |
| 986 | const KeyTag = @typeInfo(Key).Union.tag_type.?; | |
| 987 | const KeyTag = @typeInfo(Key).@"union".tag_type.?; | |
| 987 | 988 | const seed = @intFromEnum(@as(KeyTag, key)); |
| 988 | 989 | |
| 989 | 990 | return switch (key) { |
| ... | ... | @@ -1000,6 +1001,7 @@ pub const Key = union(enum) { |
| 1000 | 1001 | .struct_value, |
| 1001 | 1002 | .array_value, |
| 1002 | 1003 | .array_type, |
| 1004 | .external_value, | |
| 1003 | 1005 | => |x| { |
| 1004 | 1006 | var hasher = Hash.init(seed); |
| 1005 | 1007 | x.hash(&hasher); |
| ... | ... | @@ -1013,7 +1015,7 @@ pub const Key = union(enum) { |
| 1013 | 1015 | const t = tracer.trace(@src()); |
| 1014 | 1016 | defer t.end(); |
| 1015 | 1017 | |
| 1016 | const KeyTag = @typeInfo(Key).Union.tag_type.?; | |
| 1018 | const KeyTag = @typeInfo(Key).@"union".tag_type.?; | |
| 1017 | 1019 | const a_tag: KeyTag = a; |
| 1018 | 1020 | const b_tag: KeyTag = b; |
| 1019 | 1021 | if (a_tag != b_tag) return false; |
| ... | ... | @@ -1053,6 +1055,10 @@ pub const Key = union(enum) { |
| 1053 | 1055 | return a_info.type == b_info.type and |
| 1054 | 1056 | std.mem.eql(Value, a_info.items, b_info.items); |
| 1055 | 1057 | }, |
| 1058 | .external_value => |a_info| { | |
| 1059 | const b_info = b.external_value; | |
| 1060 | return std.mem.eql(NullTerminatedString, a_info.key, b_info.key); | |
| 1061 | }, | |
| 1056 | 1062 | }; |
| 1057 | 1063 | } |
| 1058 | 1064 | }; |
| ... | ... | @@ -1079,9 +1085,113 @@ const EmbeddedNulls = enum { |
| 1079 | 1085 | } |
| 1080 | 1086 | }; |
| 1081 | 1087 | |
| 1088 | /// Represents a single entity which undergoes semantic analysis. | |
| 1089 | /// This is the "source" of an incremental dependency edge. | |
| 1090 | pub const AnalysisUnit = packed struct(u32) { | |
| 1091 | kind: enum(u1) { decl, func }, | |
| 1092 | index: u31, | |
| 1093 | ||
| 1094 | pub const Unwrapped = union(enum) { | |
| 1095 | decl: DeclIndex, | |
| 1096 | func: Value, | |
| 1097 | }; | |
| 1098 | ||
| 1099 | pub fn unwrap(as: AnalysisUnit) Unwrapped { | |
| 1100 | return switch (as.kind) { | |
| 1101 | .decl => .{ .decl = @enumFromInt(as.index) }, | |
| 1102 | .func => .{ .func = @enumFromInt(as.index) }, | |
| 1103 | }; | |
| 1104 | } | |
| 1105 | ||
| 1106 | pub fn wrap(raw: Unwrapped) AnalysisUnit { | |
| 1107 | return switch (raw) { | |
| 1108 | .decl => |decl| .{ .kind = .decl, .index = @intCast(@intFromEnum(decl)) }, | |
| 1109 | .func => |func| .{ .kind = .func, .index = @intCast(@intFromEnum(func)) }, | |
| 1110 | }; | |
| 1111 | } | |
| 1112 | ||
| 1113 | pub fn toOptional(as: AnalysisUnit) Optional { | |
| 1114 | return @enumFromInt(@as(u32, @bitCast(as))); | |
| 1115 | } | |
| 1116 | ||
| 1117 | pub const Optional = enum(u32) { | |
| 1118 | none = std.math.maxInt(u32), | |
| 1119 | _, | |
| 1120 | ||
| 1121 | pub fn unwrap(opt: Optional) ?AnalysisUnit { | |
| 1122 | return switch (opt) { | |
| 1123 | .none => null, | |
| 1124 | _ => @bitCast(@intFromEnum(opt)), | |
| 1125 | }; | |
| 1126 | } | |
| 1127 | }; | |
| 1128 | ||
| 1129 | pub fn dump(au: AnalysisUnit, ip: *const InternPool) std.fmt.Formatter(formatImpl) { | |
| 1130 | return .{ .data = .{ .au = au, .ip = ip } }; | |
| 1131 | } | |
| 1132 | ||
| 1133 | fn formatImpl( | |
| 1134 | data: struct { au: AnalysisUnit, ip: *const InternPool }, | |
| 1135 | comptime unused: []const u8, | |
| 1136 | _: std.fmt.FormatOptions, | |
| 1137 | w: std.io.AnyWriter, | |
| 1138 | ) !void { | |
| 1139 | comptime assert(unused.len == 0); | |
| 1140 | try w.print("{s}: ", .{@tagName(data.au.kind)}); | |
| 1141 | switch (data.au.unwrap()) { | |
| 1142 | .decl => |index| try w.print("^{d}", .{@intFromEnum(index)}), | |
| 1143 | .func => |value| try w.print("'{s}'", .{ | |
| 1144 | data.ip.declPtrConst(value.toKey(data.ip).func.owner_decl).name.toSlice(data.ip), | |
| 1145 | }), | |
| 1146 | } | |
| 1147 | } | |
| 1148 | }; | |
| 1149 | ||
| 1150 | pub const Dependee = union(enum) { | |
| 1151 | src_hash: TrackedInst.Index, | |
| 1152 | decl_val: DeclIndex, | |
| 1153 | }; | |
| 1154 | ||
| 1155 | pub const DepEntry = extern struct { | |
| 1156 | /// If null, this is a dummy entry. `next_dependee` is undefined. This is the first | |
| 1157 | /// entry in one of `*_deps`, and does not appear in any list by `first_dependency`, | |
| 1158 | /// but is not in `free_dep_entries` since `*_deps` stores a reference to it. | |
| 1159 | depender: AnalysisUnit.Optional, | |
| 1160 | /// Index into `dep_entries` forming a doubly linked list of all dependencies on this dependee. | |
| 1161 | /// Used to iterate all dependers for a given dependee during an update. | |
| 1162 | /// null if this is the end of the list. | |
| 1163 | next: DepEntry.Index.Optional, | |
| 1164 | /// The other link for `next`. | |
| 1165 | /// null if this is the start of the list. | |
| 1166 | prev: DepEntry.Index.Optional, | |
| 1167 | /// Index into `dep_entries` forming a singly linked list of dependencies *of* `depender`. | |
| 1168 | /// Used to efficiently remove all `DepEntry`s for a single `depender` when it is re-analyzed. | |
| 1169 | /// null if this is the end of the list. | |
| 1170 | next_dependee: DepEntry.Index.Optional, | |
| 1171 | ||
| 1172 | pub const Index = enum(u32) { | |
| 1173 | _, | |
| 1174 | ||
| 1175 | pub fn toOptional(dep: DepEntry.Index) Optional { | |
| 1176 | return @enumFromInt(@intFromEnum(dep)); | |
| 1177 | } | |
| 1178 | ||
| 1179 | pub const Optional = enum(u32) { | |
| 1180 | none = std.math.maxInt(u32), | |
| 1181 | _, | |
| 1182 | pub fn unwrap(opt: Optional) ?DepEntry.Index { | |
| 1183 | return switch (opt) { | |
| 1184 | .none => null, | |
| 1185 | _ => @enumFromInt(@intFromEnum(opt)), | |
| 1186 | }; | |
| 1187 | } | |
| 1188 | }; | |
| 1189 | }; | |
| 1190 | }; | |
| 1191 | ||
| 1082 | 1192 | // implementations: |
| 1083 | 1193 | |
| 1084 | /// To construct, instantiate `.{ .gpa = gpa }` then call this. | |
| 1194 | /// To construct, instantiate `.{}` then call this. | |
| 1085 | 1195 | pub fn init(ip: *InternPool, gpa: Allocator) !void { |
| 1086 | 1196 | const t = tracer.trace(@src()); |
| 1087 | 1197 | defer t.end(); |
| ... | ... | @@ -1102,7 +1212,7 @@ pub fn init(ip: *InternPool, gpa: Allocator) !void { |
| 1102 | 1212 | _ = ip.get(gpa, key) catch unreachable; |
| 1103 | 1213 | } |
| 1104 | 1214 | |
| 1105 | if (std.debug.runtime_safety) { | |
| 1215 | if (core.runtime_safety) { | |
| 1106 | 1216 | // Sanity check. |
| 1107 | 1217 | assert(ip.valueToKey(.bool_true).simple_value == .true); |
| 1108 | 1218 | assert(ip.valueToKey(.bool_false).simple_value == .false); |
| ... | ... | @@ -1139,16 +1249,16 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { |
| 1139 | 1249 | |
| 1140 | 1250 | ip.tracked_insts.deinit(gpa); |
| 1141 | 1251 | |
| 1142 | // ip.src_hash_deps.deinit(gpa); | |
| 1143 | // ip.decl_val_deps.deinit(gpa); | |
| 1252 | ip.src_hash_deps.deinit(gpa); | |
| 1253 | ip.decl_val_deps.deinit(gpa); | |
| 1144 | 1254 | // ip.func_ies_deps.deinit(gpa); |
| 1145 | 1255 | // ip.namespace_deps.deinit(gpa); |
| 1146 | 1256 | // ip.namespace_name_deps.deinit(gpa); |
| 1147 | 1257 | |
| 1148 | // ip.first_dependency.deinit(gpa); | |
| 1258 | ip.first_dependency.deinit(gpa); | |
| 1149 | 1259 | |
| 1150 | // ip.dep_entries.deinit(gpa); | |
| 1151 | // ip.free_dep_entries.deinit(gpa); | |
| 1260 | ip.dep_entries.deinit(gpa); | |
| 1261 | ip.free_dep_entries.deinit(gpa); | |
| 1152 | 1262 | |
| 1153 | 1263 | ip.* = undefined; |
| 1154 | 1264 | } |
| ... | ... | @@ -1212,6 +1322,12 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key { |
| 1212 | 1322 | .items = trail.readValueSlice(payload.items_len).get(ip), |
| 1213 | 1323 | } }; |
| 1214 | 1324 | }, |
| 1325 | .external_value => { | |
| 1326 | const payload: Tag.ExternalValue, var trail = data.decode(.external_value, item.tag).getTrail(ip); | |
| 1327 | return .{ .external_value = .{ | |
| 1328 | .key = trail.readNullTerminatedStringSlice(payload.key_count).get(ip), | |
| 1329 | } }; | |
| 1330 | }, | |
| 1215 | 1331 | }; |
| 1216 | 1332 | } |
| 1217 | 1333 | |
| ... | ... | @@ -1307,10 +1423,17 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { |
| 1307 | 1423 | trail.writeSlice(s.items); |
| 1308 | 1424 | break :brk Item.encode(.array_value, extra); |
| 1309 | 1425 | }, |
| 1426 | .external_value => |s| brk: { | |
| 1427 | const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.ExternalValue, .{ | |
| 1428 | .key_count = @intCast(s.key.len), | |
| 1429 | }, s.key.len); | |
| 1430 | trail.writeSlice(s.key); | |
| 1431 | break :brk Item.encode(.external_value, extra); | |
| 1432 | }, | |
| 1310 | 1433 | }; |
| 1311 | 1434 | ip.items.appendAssumeCapacity(item); |
| 1312 | 1435 | const index: Value = @enumFromInt(ip.items.len - 1); |
| 1313 | if (std.debug.runtime_safety) { | |
| 1436 | if (core.runtime_safety) { | |
| 1314 | 1437 | const key_added = ip.valueToKey(index); |
| 1315 | 1438 | assert(key == .struct_type or key_added.eql(key, ip)); |
| 1316 | 1439 | } |
| ... | ... | @@ -1398,6 +1521,53 @@ pub fn trackInst(ip: *InternPool, gpa: Allocator, file: *Comp.File, inst: Teg.In |
| 1398 | 1521 | return @enumFromInt(gop.index); |
| 1399 | 1522 | } |
| 1400 | 1523 | |
| 1524 | pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalysisUnit, dependee: Dependee) Allocator.Error!void { | |
| 1525 | const first_depender_dep: DepEntry.Index.Optional = if (ip.first_dependency.get(depender)) |idx| dep: { | |
| 1526 | // The entry already exists, so there is capacity to overwrite it later. | |
| 1527 | break :dep idx.toOptional(); | |
| 1528 | } else none: { | |
| 1529 | // Ensure there is capacity available to add this dependency later. | |
| 1530 | try ip.first_dependency.ensureUnusedCapacity(gpa, 1); | |
| 1531 | break :none .none; | |
| 1532 | }; | |
| 1533 | ||
| 1534 | // We're very likely to need space for a new entry - reserve it now to avoid | |
| 1535 | // the need for error cleanup logic. | |
| 1536 | if (ip.free_dep_entries.items.len == 0) { | |
| 1537 | try ip.dep_entries.ensureUnusedCapacity(gpa, 1); | |
| 1538 | } | |
| 1539 | ||
| 1540 | // This block should allocate an entry and prepend it to the relevant `*_deps` list. | |
| 1541 | // The `next` field should be correctly initialized; all other fields may be undefined. | |
| 1542 | const new_index: DepEntry.Index = switch (dependee) { | |
| 1543 | inline else => |dependee_payload, tag| new_index: { | |
| 1544 | const gop = try switch (tag) { | |
| 1545 | .src_hash => ip.src_hash_deps, | |
| 1546 | .decl_val => ip.decl_val_deps, | |
| 1547 | }.getOrPut(gpa, dependee_payload); | |
| 1548 | ||
| 1549 | if (gop.found_existing and ip.dep_entries.items[@intFromEnum(gop.value_ptr.*)].depender == .none) { | |
| 1550 | // Dummy entry, so we can reuse it rather than allocating a new one! | |
| 1551 | ip.dep_entries.items[@intFromEnum(gop.value_ptr.*)].next = .none; | |
| 1552 | break :new_index gop.value_ptr.*; | |
| 1553 | } | |
| 1554 | ||
| 1555 | // Prepend a new dependency. | |
| 1556 | const new_index: DepEntry.Index, const ptr = if (ip.free_dep_entries.popOrNull()) |new_index| new: { | |
| 1557 | break :new .{ new_index, &ip.dep_entries.items[@intFromEnum(new_index)] }; | |
| 1558 | } else .{ @enumFromInt(ip.dep_entries.items.len), ip.dep_entries.addOneAssumeCapacity() }; | |
| 1559 | ptr.next = if (gop.found_existing) gop.value_ptr.*.toOptional() else .none; | |
| 1560 | gop.value_ptr.* = new_index; | |
| 1561 | break :new_index new_index; | |
| 1562 | }, | |
| 1563 | }; | |
| 1564 | ||
| 1565 | ip.dep_entries.items[@intFromEnum(new_index)].depender = depender.toOptional(); | |
| 1566 | ip.dep_entries.items[@intFromEnum(new_index)].prev = .none; | |
| 1567 | ip.dep_entries.items[@intFromEnum(new_index)].next_dependee = first_depender_dep; | |
| 1568 | ip.first_dependency.putAssumeCapacity(depender, new_index); | |
| 1569 | } | |
| 1570 | ||
| 1401 | 1571 | pub fn declPtr(ip: *InternPool, index: DeclIndex) *Comp.Decl { |
| 1402 | 1572 | return ip.allocated_decls.at(@intFromEnum(index)); |
| 1403 | 1573 | } |
src/core/Parse.zig+103-71| ... | ... | @@ -18,24 +18,7 @@ depth: u32 = 0, |
| 18 | 18 | const depth_limit = 4096; |
| 19 | 19 | |
| 20 | 20 | pub fn parse(gpa: std.mem.Allocator, source: [:0]const u8, ast_error: *AstError.WithTrace) Parse.Error!Ast { |
| 21 | var err_byte: ByteOffset = 0; | |
| 22 | var tokens = Tokenizer.tokenize( | |
| 23 | source, | |
| 24 | gpa, | |
| 25 | &err_byte, | |
| 26 | ) catch |err| switch (err) { | |
| 27 | error.OutOfMemory => return error.OutOfMemory, | |
| 28 | else => { | |
| 29 | ast_error.* = .{ | |
| 30 | .err = .{ .tokenization = .{ | |
| 31 | .err = err, | |
| 32 | .byte = err_byte, | |
| 33 | } }, | |
| 34 | .trace = DebugStoredTrace.from(@errorReturnTrace()), | |
| 35 | }; | |
| 36 | return error.ParseFail; | |
| 37 | }, | |
| 38 | }; | |
| 21 | var tokens = try Tokenizer.tokenize(source, gpa); | |
| 39 | 22 | errdefer tokens.deinit(gpa); |
| 40 | 23 | |
| 41 | 24 | var parser = Parse.init(gpa, source, tokens); |
| ... | ... | @@ -125,11 +108,11 @@ pub const ExpectedContext = enum { |
| 125 | 108 | /// When printing these, token list and Ast is not actually available, so full tokens |
| 126 | 109 | /// are stored in the errors. |
| 127 | 110 | pub const AstError = union(enum) { |
| 128 | /// Failure during tokenization | |
| 129 | tokenization: struct { | |
| 130 | err: Tokenizer.TokenizeError, | |
| 131 | byte: ByteOffset, | |
| 132 | }, | |
| 111 | // /// Failure during tokenization | |
| 112 | // tokenization: struct { | |
| 113 | // err: Tokenizer.TokenizeError, | |
| 114 | // byte: ByteOffset, | |
| 115 | // }, | |
| 133 | 116 | /// Unexpected data, expected a token. |
| 134 | 117 | unexpected_token: struct { |
| 135 | 118 | expected: Token.Tag, |
| ... | ... | @@ -145,29 +128,29 @@ pub const AstError = union(enum) { |
| 145 | 128 | |
| 146 | 129 | array_type_flipped_hint: Token, |
| 147 | 130 | |
| 148 | pub fn toReport(err: AstError, comp: *Comp, file: *Comp.File, trace: DebugStoredTrace) !Report { | |
| 149 | return (Report.Err{ .ast = err }).toReport(comp, file, trace); | |
| 131 | pub fn toReport(err: AstError, allocator: Allocator, trace: DebugStoredTrace) !Report { | |
| 132 | return (Report.Err{ .ast = err }).toReport(allocator, trace); | |
| 150 | 133 | } |
| 151 | 134 | |
| 152 | 135 | pub fn build(err: AstError, ctx: *Report.MsgContext) !void { |
| 153 | 136 | switch (err) { |
| 154 | .tokenization => |data| { | |
| 155 | // TODO: consider making tokenizer emit AstError more directly, | |
| 156 | // so that it can just annotate start and ends | |
| 157 | try ctx.msg(switch (data.err) { | |
| 158 | error.UnterminatedString => .{ .codepoint_eol = data.byte }, | |
| 159 | else => .{ .codepoint = data.byte }, | |
| 160 | }, "{s}", .{switch (data.err) { | |
| 161 | error.OutOfMemory => unreachable, | |
| 162 | error.InvalidSyntax => "unexpected token", | |
| 163 | error.TrailingNumericSeparator => "trailing numeric separator", | |
| 164 | error.UnterminatedString => "unterminated string literal", | |
| 165 | error.InvalidEscapeSequence => "invalid escape sequence", | |
| 166 | error.InvalidEscapeCodepoint => "invalid escape codepoint", | |
| 167 | error.InvalidUtf8 => "file is not valid utf8", | |
| 168 | error.GitConflict => "git merge conflict encountered", | |
| 169 | }}); | |
| 170 | }, | |
| 137 | // .tokenization => |data| { | |
| 138 | // // TODO: consider making tokenizer emit AstError more directly, | |
| 139 | // // so that it can just annotate start and ends | |
| 140 | // try ctx.msg(switch (data.err) { | |
| 141 | // error.UnterminatedString => .{ .codepoint_eol = data.byte }, | |
| 142 | // else => .{ .codepoint = data.byte }, | |
| 143 | // }, "{s}", .{switch (data.err) { | |
| 144 | // error.OutOfMemory => unreachable, | |
| 145 | // error.InvalidSyntax => "unexpected token", | |
| 146 | // error.TrailingNumericSeparator => "trailing numeric separator", | |
| 147 | // error.UnterminatedString => "unterminated string literal", | |
| 148 | // error.InvalidEscapeSequence => "invalid escape sequence", | |
| 149 | // error.InvalidEscapeCodepoint => "invalid escape codepoint", | |
| 150 | // error.InvalidUtf8 => "file is not valid utf8", | |
| 151 | // error.GitConflict => "git merge conflict encountered", | |
| 152 | // }}); | |
| 153 | // }, | |
| 171 | 154 | .unexpected_token => |data| { |
| 172 | 155 | try ctx.msg(.{ .token_stored = data.got }, "expected {s}, but found {s}", .{ |
| 173 | 156 | data.expected.description(), |
| ... | ... | @@ -197,8 +180,8 @@ pub const AstError = union(enum) { |
| 197 | 180 | err: AstError, |
| 198 | 181 | trace: DebugStoredTrace, |
| 199 | 182 | |
| 200 | pub fn toReport(err: WithTrace, comp: *Comp, file: *Comp.File) !Report { | |
| 201 | return err.err.toReport(comp, file, err.trace); | |
| 183 | pub fn toReport(wt: WithTrace, allocator: Allocator) !Report { | |
| 184 | return wt.err.toReport(allocator, wt.trace); | |
| 202 | 185 | } |
| 203 | 186 | }; |
| 204 | 187 | }; |
| ... | ... | @@ -247,8 +230,13 @@ fn parseModuleMembers(p: *Parse) Error!SubList { |
| 247 | 230 | .kw_pub, |
| 248 | 231 | => try p.scratch.append(p.gpa, try p.parseModuleDecl()), |
| 249 | 232 | |
| 250 | .kw_struct, | |
| 251 | => try p.scratch.append(p.gpa, try p.parseStructDecl()), | |
| 233 | .kw_struct => try p.scratch.append(p.gpa, try p.parseStructDecl()), | |
| 234 | ||
| 235 | .id => if (p.isContextualKeyword("test")) { | |
| 236 | try p.scratch.append(p.gpa, try p.parseTestDecl()); | |
| 237 | } else { | |
| 238 | try p.parseExpressionStatement(); | |
| 239 | }, | |
| 252 | 240 | |
| 253 | 241 | .kw_enum, |
| 254 | 242 | .kw_trait, |
| ... | ... | @@ -261,25 +249,32 @@ fn parseModuleMembers(p: *Parse) Error!SubList { |
| 261 | 249 | if (!p.isExpressionStart()) { |
| 262 | 250 | return p.failUnexpectedToken(.statement); |
| 263 | 251 | } |
| 252 | try p.parseExpressionStatement(); | |
| 253 | }, | |
| 254 | }; | |
| 264 | 255 | |
| 265 | const first_tag = p.currentTag(); | |
| 256 | return p.scratchToSubList(scratch_top); | |
| 257 | } | |
| 266 | 258 | |
| 267 | try p.scratch.append(p.gpa, try p.parseExpression(.statement)); | |
| 259 | fn parseExpressionStatement(p: *Parse) !void { | |
| 260 | if (!p.isExpressionStart()) { | |
| 261 | return p.failUnexpectedToken(.statement); | |
| 262 | } | |
| 268 | 263 | |
| 269 | switch (first_tag) { | |
| 270 | // some control flow and blocks are allowed to not have | |
| 271 | // trailing semicolons. this is a stylistic choice. | |
| 272 | .kw_if, | |
| 273 | .start_brace, | |
| 274 | => {}, | |
| 264 | const first_tag = p.currentTag(); | |
| 275 | 265 | |
| 276 | // TODO: make semicolons optional, but emit a warning | |
| 277 | else => _ = try p.expect(.semi), | |
| 278 | } | |
| 279 | }, | |
| 280 | }; | |
| 266 | try p.scratch.append(p.gpa, try p.parseExpression(.statement)); | |
| 281 | 267 | |
| 282 | return p.scratchToSubList(scratch_top); | |
| 268 | switch (first_tag) { | |
| 269 | // some control flow and blocks are allowed to not have | |
| 270 | // trailing semicolons. this is a stylistic choice. | |
| 271 | .kw_if, | |
| 272 | .start_brace, | |
| 273 | => {}, | |
| 274 | ||
| 275 | // TODO: make semicolons optional, but emit a warning | |
| 276 | else => _ = try p.expect(.semi), | |
| 277 | } | |
| 283 | 278 | } |
| 284 | 279 | |
| 285 | 280 | fn parseModuleDecl(p: *Parse) Error!NodeIndex { |
| ... | ... | @@ -313,9 +308,10 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex { |
| 313 | 308 | |
| 314 | 309 | _ = try p.expect(.id); |
| 315 | 310 | |
| 316 | _ = try p.expect(.start_paren); | |
| 317 | ||
| 318 | const params = try p.parseParamList(.types_required); | |
| 311 | const params = if (p.eatToken(.start_paren) != null) | |
| 312 | try p.parseParamList(.types_required) | |
| 313 | else | |
| 314 | null; | |
| 319 | 315 | |
| 320 | 316 | const return_type = if (p.eatToken(.minus_lt)) |_| |
| 321 | 317 | if (!p.isExpressionStart()) |
| ... | ... | @@ -329,7 +325,7 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex { |
| 329 | 325 | const body = try p.parseBlock(); |
| 330 | 326 | |
| 331 | 327 | const extra = try p.addExtra(Ast.Fn, .{ |
| 332 | .params = params, | |
| 328 | .params = params orelse .empty, | |
| 333 | 329 | .return_type = optionalNodeIndex(return_type), |
| 334 | 330 | }); |
| 335 | 331 | |
| ... | ... | @@ -413,6 +409,28 @@ fn parseStructDecl(p: *Parse) Error!NodeIndex { |
| 413 | 409 | }); |
| 414 | 410 | } |
| 415 | 411 | |
| 412 | fn parseTestDecl(p: *Parse) Error!NodeIndex { | |
| 413 | const trace = t.trace(@src()); | |
| 414 | defer trace.end(); | |
| 415 | ||
| 416 | const main_token = p.assertToken(.id); | |
| 417 | ||
| 418 | _ = p.eatToken(.string); | |
| 419 | ||
| 420 | try p.expectWithoutAdvance(.start_brace); | |
| 421 | ||
| 422 | const block = try p.parseBlock(); | |
| 423 | ||
| 424 | return try p.addNode(.{ | |
| 425 | .tag = .test_decl, | |
| 426 | .main_token = main_token, | |
| 427 | .data = .{ .test_decl = .{ | |
| 428 | .doc_test_name = .none, | |
| 429 | .block = block, | |
| 430 | } }, | |
| 431 | }); | |
| 432 | } | |
| 433 | ||
| 416 | 434 | fn parseStructFieldList(p: *Parse) Error!SubList { |
| 417 | 435 | const trace = t.trace(@src()); |
| 418 | 436 | defer trace.end(); |
| ... | ... | @@ -1255,7 +1273,7 @@ fn parseArrayValueOrType(p: *Parse, mode: ParseExprMode) Error!NodeIndex { |
| 1255 | 1273 | } |
| 1256 | 1274 | |
| 1257 | 1275 | fn parseTupleType(p: *Parse) Error!NodeIndex { |
| 1258 | const main_token = p.assertToken(.start_bracket); | |
| 1276 | const main_token = p.assertToken(.start_paren); | |
| 1259 | 1277 | const list = try p.parseExpressionList(.end_paren, .expression); |
| 1260 | 1278 | return p.addNode(.{ |
| 1261 | 1279 | .tag = .tuple_type, |
| ... | ... | @@ -1334,12 +1352,7 @@ fn eatToken(p: *Parse, tag: Token.Tag) ?TokenIndex { |
| 1334 | 1352 | } |
| 1335 | 1353 | |
| 1336 | 1354 | fn expect(p: *Parse, tag: Token.Tag) !TokenIndex { |
| 1337 | if (p.currentTag() != tag) { | |
| 1338 | return p.fail(.{ .unexpected_token = .{ | |
| 1339 | .got = p.fullToken(p.pos), | |
| 1340 | .expected = tag, | |
| 1341 | } }); | |
| 1342 | } | |
| 1355 | try p.expectWithoutAdvance(tag); | |
| 1343 | 1356 | return p.nextToken(); |
| 1344 | 1357 | } |
| 1345 | 1358 | |
| ... | ... | @@ -1364,6 +1377,11 @@ fn nextToken(p: *Parse) TokenIndex { |
| 1364 | 1377 | return result; |
| 1365 | 1378 | } |
| 1366 | 1379 | |
| 1380 | fn isContextualKeyword(p: *Parse, kw: []const u8) bool { | |
| 1381 | assert(p.currentTag() == .id); | |
| 1382 | return mem.startsWith(u8, p.source[p.token_starts[p.pos]..], kw); | |
| 1383 | } | |
| 1384 | ||
| 1367 | 1385 | fn fullToken(p: *Parse, token: TokenIndex) Token { |
| 1368 | 1386 | return Token{ |
| 1369 | 1387 | .tag = p.token_tags[token], |
| ... | ... | @@ -1417,3 +1435,17 @@ const t = @import("tracer"); |
| 1417 | 1435 | |
| 1418 | 1436 | const Report = core.Report; |
| 1419 | 1437 | const Comp = core.Comp; |
| 1438 | ||
| 1439 | test "fuzz test parser for crashes" { | |
| 1440 | try core.unicode_props.init(); | |
| 1441 | ||
| 1442 | const input_bytes = std.testing.fuzzInput(.{}); | |
| 1443 | ||
| 1444 | const sentinel = try std.testing.allocator.dupeZ(u8, input_bytes); | |
| 1445 | defer std.testing.allocator.free(sentinel); | |
| 1446 | ||
| 1447 | var err: AstError.WithTrace = undefined; | |
| 1448 | var ast = parse(std.testing.allocator, sentinel, &err) catch | |
| 1449 | return; // ignore parser errors, only fail on crashes | |
| 1450 | ast.deinit(std.testing.allocator); | |
| 1451 | } |
src/core/Report.zig+233-114| ... | ... | @@ -5,9 +5,15 @@ |
| 5 | 5 | //! |
| 6 | 6 | //! Across the compiler, `Report.Builder`s' `.err` and `.warn` methods |
| 7 | 7 | //! can add messages by ID. |
| 8 | errors: []Item = &.{}, | |
| 9 | warnings: []Item = &.{}, | |
| 10 | string_bytes: []u8 = &.{}, | |
| 8 | errors: []Item, | |
| 9 | warnings: []Item, | |
| 10 | string_bytes: []u8, | |
| 11 | ||
| 12 | pub const empty: Report = .{ | |
| 13 | .errors = &.{}, | |
| 14 | .warnings = &.{}, | |
| 15 | .string_bytes = &.{}, | |
| 16 | }; | |
| 11 | 17 | |
| 12 | 18 | pub var debug_show_report_trace: if (builtin.mode == .Debug) bool else void = |
| 13 | 19 | if (builtin.mode == .Debug) false; |
| ... | ... | @@ -15,31 +21,23 @@ pub var debug_show_report_trace: if (builtin.mode == .Debug) bool else void = |
| 15 | 21 | pub const Item = struct { |
| 16 | 22 | /// What is this diagnostic |
| 17 | 23 | data: union { @"error": Err, warn: Warn }, |
| 18 | /// What file was it in | |
| 19 | url: String, | |
| 20 | 24 | |
| 21 | debug_stack_trace: DebugStoredTrace, | |
| 25 | debug_stack_trace: DebugStoredTrace = .empty, | |
| 22 | 26 | |
| 23 | 27 | /// Type is never stored, inferred from which list it is in. |
| 24 | 28 | pub const Type = enum { @"error", warn }; |
| 25 | 29 | }; |
| 26 | 30 | |
| 27 | 31 | pub const Err = union(enum) { |
| 28 | pub const FieldEnum = std.meta.FieldEnum(Err); | |
| 32 | pub const FieldEnum = core.FieldEnum(Err); | |
| 29 | 33 | |
| 30 | 34 | /// Constructs a `Report` with just a single error. |
| 31 | pub fn toReport(err: Err, comp: *Comp, file: *File, trace: DebugStoredTrace) !Report { | |
| 32 | const string_bytes = try comp.gpa.dupe(u8, file.file_path); | |
| 33 | errdefer comp.gpa.free(string_bytes); | |
| 34 | ||
| 35 | const errors = try comp.gpa.dupe(Item, &.{.{ | |
| 35 | pub fn toReport(err: Err, alloc: Allocator, trace: DebugStoredTrace) !Report { | |
| 36 | const errors = try alloc.dupe(Item, &.{.{ | |
| 36 | 37 | .data = .{ .@"error" = err }, |
| 37 | .url = .{ .start = 0, .len = @intCast(string_bytes.len) }, | |
| 38 | 38 | .debug_stack_trace = trace, |
| 39 | 39 | }}); |
| 40 | errdefer comp.gpa.free(errors); | |
| 41 | ||
| 42 | return .{ .errors = errors, .string_bytes = string_bytes }; | |
| 40 | return .{ .errors = errors, .warnings = &.{}, .string_bytes = &.{} }; | |
| 43 | 41 | } |
| 44 | 42 | |
| 45 | 43 | ast: @import("Parse.zig").AstError, |
| ... | ... | @@ -54,6 +52,20 @@ pub const Err = union(enum) { |
| 54 | 52 | }); |
| 55 | 53 | } |
| 56 | 54 | }, |
| 55 | not_comptime_known: struct { | |
| 56 | src: LazyNode, | |
| 57 | ||
| 58 | fn build(data: @This(), ctx: *MsgContext) !void { | |
| 59 | try ctx.msgNode(try data.src.get(ctx), "Value must be known at compile-time", .{}); | |
| 60 | } | |
| 61 | }, | |
| 62 | cannot_be_comptime: struct { | |
| 63 | src: LazyNode, | |
| 64 | ||
| 65 | fn build(data: @This(), ctx: *MsgContext) !void { | |
| 66 | try ctx.msgNode(try data.src.get(ctx), "Operation cannot be performed at compile-time (TODO: make this error more specific)", .{}); | |
| 67 | } | |
| 68 | }, | |
| 57 | 69 | unknown_identifier: struct { |
| 58 | 70 | node: NodeIndex, |
| 59 | 71 | |
| ... | ... | @@ -82,8 +94,8 @@ pub const Err = union(enum) { |
| 82 | 94 | fn build(data: @This(), ctx: *MsgContext) !void { |
| 83 | 95 | try ctx.msgToken( |
| 84 | 96 | ctx.ast.nodeToken(data.node), |
| 85 | "unknown built-in function {}", | |
| 86 | .{formatId(ctx.ast.basicNodeSlice(data.node))}, | |
| 97 | "unknown built-in function {s}", | |
| 98 | .{ctx.ast.basicNodeSlice(data.node)}, | |
| 87 | 99 | ); |
| 88 | 100 | } |
| 89 | 101 | }, |
| ... | ... | @@ -103,10 +115,11 @@ pub const Err = union(enum) { |
| 103 | 115 | prev: NodeIndex, |
| 104 | 116 | |
| 105 | 117 | fn build(data: @This(), ctx: *MsgContext) !void { |
| 106 | try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of {}", .{ | |
| 107 | formatId(ctx.ast.fullFnDecl(data.node).id), | |
| 118 | const id_token = ctx.ast.idToken(data.node); | |
| 119 | try ctx.msgToken(id_token, "re-declaration of {}", .{ | |
| 120 | formatId(ctx.ast.tokenSlice(id_token)), | |
| 108 | 121 | }); |
| 109 | try ctx.msgToken(ctx.ast.fnDeclIdToken(data.prev), "also declared here", .{}); | |
| 122 | try ctx.msgToken(ctx.ast.idToken(data.prev), "also declared here", .{}); | |
| 110 | 123 | // TODO: be able to store a full list of other declarations |
| 111 | 124 | // and add them here as "and here" and so on |
| 112 | 125 | } |
| ... | ... | @@ -177,6 +190,14 @@ pub const Err = union(enum) { |
| 177 | 190 | try ctx.msgNode(node, "numeric literal \"{d}\" cannot be coerced to \"int\"", .{data.val}); |
| 178 | 191 | } |
| 179 | 192 | }, |
| 193 | top_level_statement: struct { | |
| 194 | src: NodeIndex, | |
| 195 | ||
| 196 | fn build(data: @This(), ctx: *MsgContext) !void { | |
| 197 | try ctx.msgNode(data.src, "top-level statements are not allowed", .{}); | |
| 198 | try ctx.msgAlone("to run code on application startup, add a 'fn main'", .{}); | |
| 199 | } | |
| 200 | }, | |
| 180 | 201 | coercion: struct { |
| 181 | 202 | from: Type, |
| 182 | 203 | to: Type, |
| ... | ... | @@ -535,6 +556,29 @@ pub const Warn = union(enum) { |
| 535 | 556 | }, |
| 536 | 557 | }; |
| 537 | 558 | |
| 559 | pub const DumpContext = union(enum) { | |
| 560 | /// Reports generated from Semantic Analysis require full | |
| 561 | /// compilation state, notably InternPool to read values. | |
| 562 | comp: struct { | |
| 563 | comp: *Comp, | |
| 564 | file: *Comp.File, | |
| 565 | }, | |
| 566 | /// Reports generated from Parse and Transform are | |
| 567 | /// single-file, and can be dumped without full state. | |
| 568 | single_file: struct { | |
| 569 | allocator: Allocator, | |
| 570 | url: []const u8, | |
| 571 | file: union(enum) { | |
| 572 | utf8_source: [:0]const u8, | |
| 573 | ast: *const Ast, | |
| 574 | }, | |
| 575 | }, | |
| 576 | ||
| 577 | pub fn initComp(comp: *Comp, file: *Comp.File) DumpContext { | |
| 578 | return .{ .comp = .{ .comp = comp, .file = file } }; | |
| 579 | } | |
| 580 | }; | |
| 581 | ||
| 538 | 582 | pub const UnusedKind = enum { |
| 539 | 583 | param, |
| 540 | 584 | constant, |
| ... | ... | @@ -579,50 +623,96 @@ pub fn deinit(report: Report, gpa: Allocator) void { |
| 579 | 623 | gpa.free(report.string_bytes); |
| 580 | 624 | } |
| 581 | 625 | |
| 582 | pub fn dumpToWriter(report: Report, comp: *Comp, writer: anytype, tty: std.io.tty.Config) !void { | |
| 583 | // try writer.print("{d} errors, {d} warnings\n", .{ report.errors.len, report.warnings.len }); | |
| 626 | pub fn dump(report: Report, ctx: DumpContext, out: core.OutStream) !void { | |
| 627 | var arena = std.heap.ArenaAllocator.init(switch (ctx) { | |
| 628 | .comp => |comp_ctx| comp_ctx.comp.gpa, | |
| 629 | .single_file => |single_file_ctx| single_file_ctx.allocator, | |
| 630 | }); | |
| 631 | defer arena.deinit(); | |
| 632 | ||
| 633 | var fake_ast_data: Ast = undefined; | |
| 634 | ||
| 635 | const ast: *const Ast = switch (ctx) { | |
| 636 | .comp => |comp_ctx| | |
| 637 | // Ast cannot be queried if the error is in computing the Ast | |
| 638 | if (report.errors.len > 0 and report.errors[0].data.@"error" == .ast) fake_ast: { | |
| 639 | fake_ast_data = .{ | |
| 640 | .utf8_source = (comp_ctx.file.getSource(comp_ctx.comp.gpa) catch { | |
| 641 | @panic("TODO"); | |
| 642 | // try report.dumpErrorDuringDump(report, .get_source, err, @errorReturnTrace()), | |
| 643 | // return; | |
| 644 | }).code, | |
| 645 | .tokens = (Tokenizer.Token.List{}).slice(), | |
| 646 | .nodes = (Ast.Node.List{}).slice(), | |
| 647 | .extra = &.{}, | |
| 648 | }; | |
| 649 | break :fake_ast &fake_ast_data; | |
| 650 | } else comp_ctx.file.getAst(comp_ctx.comp) catch { | |
| 651 | @panic("TODO"); | |
| 652 | // try report.dumpErrorDuringDump(report, .get_ast, err, @errorReturnTrace()), | |
| 653 | // return; | |
| 654 | }, | |
| 655 | .single_file => |single_file_ctx| switch (single_file_ctx.file) { | |
| 656 | .ast => |ast| ast, | |
| 657 | .utf8_source => |source| fake_ast: { | |
| 658 | fake_ast_data = .{ | |
| 659 | .utf8_source = source, | |
| 660 | .tokens = (Tokenizer.Token.List{}).slice(), | |
| 661 | .nodes = (Ast.Node.List{}).slice(), | |
| 662 | .extra = &.{}, | |
| 663 | }; | |
| 664 | break :fake_ast &fake_ast_data; | |
| 665 | }, | |
| 666 | }, | |
| 667 | }; | |
| 668 | ||
| 669 | const url = switch (ctx) { | |
| 670 | .comp => |comp_ctx| comp_ctx.file.file_path, | |
| 671 | .single_file => |file_ctx| file_ctx.url, | |
| 672 | }; | |
| 673 | ||
| 674 | const msg_ctx: MsgContext = .{ | |
| 675 | .report = &report, | |
| 676 | .comp = switch (ctx) { | |
| 677 | .comp => |comp_ctx| comp_ctx.comp, | |
| 678 | else => null, | |
| 679 | }, | |
| 680 | .file = switch (ctx) { | |
| 681 | .comp => |comp_ctx| comp_ctx.file, | |
| 682 | else => null, | |
| 683 | }, | |
| 684 | .ast = ast, | |
| 685 | .utf8_source = ast.utf8_source, | |
| 686 | ||
| 687 | .arena = undefined, | |
| 688 | .components = undefined, | |
| 689 | }; | |
| 690 | ||
| 584 | 691 | for (report.errors) |item| { |
| 585 | try report.formatItem(item, .@"error", comp, writer, tty); | |
| 692 | try formatItem(item, .@"error", arena.allocator(), url, msg_ctx, out); | |
| 693 | _ = arena.reset(.retain_capacity); | |
| 586 | 694 | } |
| 587 | 695 | for (report.warnings) |item| { |
| 588 | try report.formatItem(item, .warn, comp, writer, tty); | |
| 696 | try formatItem(item, .warn, arena.allocator(), url, msg_ctx, out); | |
| 697 | _ = arena.reset(.retain_capacity); | |
| 589 | 698 | } |
| 590 | 699 | } |
| 591 | 700 | |
| 592 | pub fn formatItem( | |
| 593 | report: *const Report, | |
| 701 | fn formatItem( | |
| 594 | 702 | item: Item, |
| 595 | 703 | item_type: Item.Type, |
| 596 | comp: *Comp, | |
| 597 | writer: anytype, | |
| 598 | tty: std.io.tty.Config, | |
| 704 | arena: Allocator, | |
| 705 | url: []const u8, | |
| 706 | ctx_init: MsgContext, | |
| 707 | out: core.OutStream, | |
| 599 | 708 | ) !void { |
| 600 | var arena = std.heap.ArenaAllocator.init(comp.gpa); | |
| 601 | defer arena.deinit(); | |
| 602 | var sfa = std.heap.stackFallback(16384, arena.allocator()); | |
| 709 | var sfa = std.heap.stackFallback(16384, arena); | |
| 603 | 710 | const alloc = sfa.get(); |
| 604 | 711 | |
| 605 | const file = try comp.getFile(item.url.slice(report)); | |
| 606 | ||
| 607 | const need_load_ast = !(item_type == .@"error" and item.data.@"error" == .ast); | |
| 608 | const ast_data = if (need_load_ast) | |
| 609 | try file.getAst(comp) | |
| 610 | else brk: { | |
| 611 | var ast: Ast = undefined; | |
| 612 | ast.utf8_source = (try file.getSource(comp.gpa)).code; | |
| 613 | break :brk ast; | |
| 614 | }; | |
| 615 | ||
| 616 | var ctx: MsgContext = .{ | |
| 617 | .arena = alloc, | |
| 618 | .comp = comp, | |
| 619 | .file = file, | |
| 620 | .ast = &ast_data, | |
| 621 | .report = report, | |
| 622 | .utf8_source = (try file.getSource(comp.gpa)).code, | |
| 623 | .components = ArrayListUnmanaged(Component).initCapacity(alloc, 16) catch | |
| 624 | unreachable, // stack-fallback allocator has enough space. | |
| 625 | }; | |
| 712 | var ctx: MsgContext = ctx_init; | |
| 713 | ctx.arena = alloc; | |
| 714 | ctx.components = ArrayListUnmanaged(Component).initCapacity(alloc, 16) catch | |
| 715 | unreachable; // stack-fallback allocator has enough space. | |
| 626 | 716 | |
| 627 | 717 | switch (item_type) { |
| 628 | 718 | inline else => |tag| switch (@field(item.data, @tagName(tag))) { |
| ... | ... | @@ -632,6 +722,9 @@ pub fn formatItem( |
| 632 | 722 | |
| 633 | 723 | assert(ctx.components.items.len > 0); |
| 634 | 724 | |
| 725 | const writer = out.writer; | |
| 726 | const tty = out.tty; | |
| 727 | ||
| 635 | 728 | const width = brk: { |
| 636 | 729 | var max: usize = 0; |
| 637 | 730 | for (ctx.components.items) |component| { |
| ... | ... | @@ -704,7 +797,7 @@ pub fn formatItem( |
| 704 | 797 | try writer.writeByteNTimes(' ', width); |
| 705 | 798 | try writer.print("{s}{s}:{d}:{d}\n", .{ |
| 706 | 799 | if (i == 1) "┬ " else "├ ", |
| 707 | file.file_path, | |
| 800 | url, | |
| 708 | 801 | at.loc.line.oneBased(), |
| 709 | 802 | at.loc.column.oneBased(), |
| 710 | 803 | }); |
| ... | ... | @@ -740,52 +833,58 @@ pub fn formatItem( |
| 740 | 833 | } |
| 741 | 834 | } |
| 742 | 835 | |
| 743 | pub fn dumpToStdErr(report: Report, comp: *Comp) !void { | |
| 744 | const stderr = std.io.getStdErr(); | |
| 745 | const tty = std.io.tty.detectConfig(stderr); | |
| 746 | ||
| 747 | if (false or | |
| 748 | // Windows API uses its own API calls, so a buffered writer | |
| 749 | // cannot be used :/ | |
| 750 | (builtin.os.tag == .windows and tty == .windows_api) or | |
| 751 | // Debug stack trace printer writes directly to standard err, | |
| 752 | // so we need to be unbuffered for that. | |
| 753 | (builtin.mode == .Debug and debug_show_report_trace)) | |
| 754 | { | |
| 755 | try dumpToWriter(report, comp, stderr.writer(), tty); | |
| 756 | } else { | |
| 757 | var buf = std.io.bufferedWriter(stderr.writer()); | |
| 758 | const writer = buf.writer(); | |
| 759 | try dumpToWriter(report, comp, writer, tty); | |
| 760 | try buf.flush(); | |
| 761 | } | |
| 762 | } | |
| 836 | // pub fn dumpToStdErr(report: Report, comp: *Comp) !void { | |
| 837 | // const stderr = std.io.getStdErr(); | |
| 838 | // const tty = std.io.tty.detectConfig(stderr); | |
| 839 | ||
| 840 | // if (false or | |
| 841 | // // Windows API uses its own API calls, so a buffered writer | |
| 842 | // // cannot be used :/ | |
| 843 | // (builtin.os.tag == .windows and tty == .windows_api) or | |
| 844 | // // Debug stack trace printer writes directly to standard err, | |
| 845 | // // so we need to be unbuffered for that. | |
| 846 | // (builtin.mode == .Debug and debug_show_report_trace)) | |
| 847 | // { | |
| 848 | // try dumpToWriter(report, comp, stderr.writer(), tty); | |
| 849 | // } else { | |
| 850 | // var buf = std.io.bufferedWriter(stderr.writer()); | |
| 851 | // const writer = buf.writer(); | |
| 852 | // try dumpToWriter(report, comp, writer, tty); | |
| 853 | // try buf.flush(); | |
| 854 | // } | |
| 855 | // } | |
| 763 | 856 | |
| 764 | 857 | pub const Builder = struct { |
| 765 | 858 | gpa: Allocator, |
| 766 | current_file: *Comp.File, | |
| 767 | errors: ArrayListUnmanaged(Item) = .{}, | |
| 768 | warnings: ArrayListUnmanaged(Item) = .{}, | |
| 769 | string_bytes: ArrayListUnmanaged(u8) = .{}, | |
| 770 | current_file_str: ?String = null, | |
| 859 | errors: ArrayListUnmanaged(Item), | |
| 860 | warnings: ArrayListUnmanaged(Item), | |
| 861 | string_bytes: ArrayListUnmanaged(u8), | |
| 771 | 862 | |
| 772 | pub fn err(b: *Builder, comptime kind: Err.FieldEnum, data: std.meta.FieldType(Err, kind)) !void { | |
| 863 | pub fn init(gpa: Allocator) Builder { | |
| 864 | return .{ | |
| 865 | .gpa = gpa, | |
| 866 | .errors = .empty, | |
| 867 | .warnings = .empty, | |
| 868 | .string_bytes = .empty, | |
| 869 | }; | |
| 870 | } | |
| 871 | ||
| 872 | pub fn err(b: *Builder, comptime kind: Err.FieldEnum, data: core.FieldType(Err, kind)) !void { | |
| 773 | 873 | try errAt(b, kind, data, @returnAddress()); |
| 774 | 874 | } |
| 775 | 875 | |
| 776 | pub fn warn(b: *Builder, comptime kind: Warn.FieldEnum, data: std.meta.FieldType(Warn, kind)) !void { | |
| 876 | pub fn warn(b: *Builder, comptime kind: Warn.FieldEnum, data: core.FieldType(Warn, kind)) !void { | |
| 777 | 877 | try warnAt(b, kind, data, @returnAddress()); |
| 778 | 878 | } |
| 779 | 879 | |
| 780 | 880 | pub fn errAt( |
| 781 | 881 | b: *Builder, |
| 782 | 882 | comptime kind: Err.FieldEnum, |
| 783 | data: std.meta.FieldType(Err, kind), | |
| 883 | data: core.FieldType(Err, kind), | |
| 784 | 884 | return_address: usize, |
| 785 | 885 | ) !void { |
| 786 | 886 | try b.errors.append(b.gpa, .{ |
| 787 | 887 | .data = .{ .@"error" = @unionInit(Err, @tagName(kind), data) }, |
| 788 | .url = try b.currentFileString(), | |
| 789 | 888 | .debug_stack_trace = DebugStoredTrace.capture(return_address), |
| 790 | 889 | }); |
| 791 | 890 | } |
| ... | ... | @@ -793,12 +892,11 @@ pub const Builder = struct { |
| 793 | 892 | pub fn warnAt( |
| 794 | 893 | b: *Builder, |
| 795 | 894 | comptime kind: Warn.FieldEnum, |
| 796 | data: std.meta.FieldType(Warn, kind), | |
| 895 | data: core.FieldType(Warn, kind), | |
| 797 | 896 | return_address: usize, |
| 798 | 897 | ) !void { |
| 799 | 898 | try b.warnings.append(b.gpa, .{ |
| 800 | 899 | .data = .{ .warn = @unionInit(Warn, @tagName(kind), data) }, |
| 801 | .url = try b.currentFileString(), | |
| 802 | 900 | .debug_stack_trace = DebugStoredTrace.capture(return_address), |
| 803 | 901 | }); |
| 804 | 902 | } |
| ... | ... | @@ -823,17 +921,14 @@ pub const Builder = struct { |
| 823 | 921 | } |
| 824 | 922 | |
| 825 | 923 | pub fn finalize(b: *Builder) !Report { |
| 826 | defer b.* = undefined; | |
| 827 | errdefer b.deinit(); | |
| 828 | ||
| 829 | 924 | const errors = try b.errors.toOwnedSlice(b.gpa); |
| 830 | errdefer b.gpa.free(errors); | |
| 925 | errdefer b.errors = .fromOwnedSlice(errors); | |
| 831 | 926 | |
| 832 | 927 | const warnings = try b.warnings.toOwnedSlice(b.gpa); |
| 833 | errdefer b.gpa.free(warnings); | |
| 928 | errdefer b.warnings = .fromOwnedSlice(warnings); | |
| 834 | 929 | |
| 835 | 930 | const string_bytes = try b.string_bytes.toOwnedSlice(b.gpa); |
| 836 | errdefer b.gpa.free(string_bytes); | |
| 931 | errdefer b.string_bytes = .fromOwnedSlice(string_bytes); | |
| 837 | 932 | |
| 838 | 933 | return .{ |
| 839 | 934 | .errors = errors, |
| ... | ... | @@ -943,8 +1038,8 @@ pub const Highlight = union(enum) { |
| 943 | 1038 | |
| 944 | 1039 | /// This serves as a small API to make creating messages easier. |
| 945 | 1040 | pub const MsgContext = struct { |
| 946 | file: *const File, | |
| 947 | comp: *const Comp, | |
| 1041 | file: ?*const File, | |
| 1042 | comp: ?*const Comp, | |
| 948 | 1043 | ast: *const Ast, |
| 949 | 1044 | report: *const Report, |
| 950 | 1045 | utf8_source: [:0]const u8, |
| ... | ... | @@ -981,18 +1076,19 @@ pub const MsgContext = struct { |
| 981 | 1076 | } |
| 982 | 1077 | |
| 983 | 1078 | pub fn ip(ctx: *const MsgContext) *const InternPool { |
| 984 | return &ctx.comp.intern_pool; | |
| 1079 | return &ctx.comp.?.intern_pool; | |
| 985 | 1080 | } |
| 986 | 1081 | }; |
| 987 | 1082 | |
| 988 | 1083 | pub const LazyNode = struct { |
| 1084 | // TODO: remove this, it should be inferable off of the analysis unit that belongs to the report | |
| 989 | 1085 | decl: Comp.Decl.Index, |
| 990 | 1086 | inst: Teg.Inst.Index, |
| 991 | 1087 | operand: Operand, |
| 992 | 1088 | |
| 993 | 1089 | pub fn get(lazy: LazyNode, ctx: *MsgContext) !NodeIndex { |
| 994 | const decl = ctx.comp.intern_pool.declPtrConst(lazy.decl); | |
| 995 | const ns = ctx.comp.intern_pool.namespacePtrConst(decl.src_namespace); | |
| 1090 | const decl = ctx.comp.?.intern_pool.declPtrConst(lazy.decl); | |
| 1091 | const ns = ctx.comp.?.intern_pool.namespacePtrConst(decl.src_namespace); | |
| 996 | 1092 | |
| 997 | 1093 | // TODO: This might change if we have errors like |
| 998 | 1094 | // - function arguments not matching at <callsite> |
| ... | ... | @@ -1005,7 +1101,7 @@ pub const LazyNode = struct { |
| 1005 | 1101 | @panic("TODO: LazyNode.get when the decl is in a nested namespace"); |
| 1006 | 1102 | } |
| 1007 | 1103 | |
| 1008 | const teg = &(ctx.file.teg orelse | |
| 1104 | const teg = &(ctx.file.?.teg orelse | |
| 1009 | 1105 | // this will be required once incremental compilation hits |
| 1010 | 1106 | @panic("TODO: teg was unloaded")); |
| 1011 | 1107 | |
| ... | ... | @@ -1072,7 +1168,7 @@ pub const LazyNode = struct { |
| 1072 | 1168 | else => fn_decl.params.slice(ctx.ast)[lazy.operand.getArgId()], |
| 1073 | 1169 | }; |
| 1074 | 1170 | }, |
| 1075 | .js => { | |
| 1171 | .@"extern" => { | |
| 1076 | 1172 | return base_node; |
| 1077 | 1173 | }, |
| 1078 | 1174 | .struct_init => { |
| ... | ... | @@ -1090,6 +1186,11 @@ pub const LazyNode = struct { |
| 1090 | 1186 | }, |
| 1091 | 1187 | } |
| 1092 | 1188 | }, |
| 1189 | .struct_decl => { | |
| 1190 | const fields = data.list.slice(ctx.ast); | |
| 1191 | const field = fields[lazy.operand.getArgId()]; | |
| 1192 | return ctx.ast.nodeData(field).un.expr; | |
| 1193 | }, | |
| 1093 | 1194 | .array_init => { |
| 1094 | 1195 | assert(node_tag == .array_init); |
| 1095 | 1196 | const items = data.list.slice(ctx.ast); |
| ... | ... | @@ -1101,11 +1202,21 @@ pub const LazyNode = struct { |
| 1101 | 1202 | else => {}, |
| 1102 | 1203 | } |
| 1103 | 1204 | |
| 1104 | std.debug.panic("TODO: operand lookup: .{s} + .{s} + {}", .{ | |
| 1105 | @tagName(teg.instTag(lazy.inst)), | |
| 1106 | @tagName(ctx.ast.nodeTag(base_node)), | |
| 1107 | lazy.operand, | |
| 1108 | }); | |
| 1205 | if (builtin.mode == .Debug) { | |
| 1206 | std.debug.panic("TODO: operand lookup: .{s} + .{s} + {}", .{ | |
| 1207 | @tagName(teg.instTag(lazy.inst)), | |
| 1208 | @tagName(ctx.ast.nodeTag(base_node)), | |
| 1209 | lazy.operand, | |
| 1210 | }); | |
| 1211 | } else { | |
| 1212 | std.debug.print("TODO: operand lookup: .{s} + .{s} + {}", .{ | |
| 1213 | @tagName(teg.instTag(lazy.inst)), | |
| 1214 | @tagName(ctx.ast.nodeTag(base_node)), | |
| 1215 | lazy.operand, | |
| 1216 | }); | |
| 1217 | } | |
| 1218 | ||
| 1219 | return base_node; | |
| 1109 | 1220 | } |
| 1110 | 1221 | }; |
| 1111 | 1222 | |
| ... | ... | @@ -1125,18 +1236,29 @@ pub const Operand = enum(u32) { |
| 1125 | 1236 | /// One of: |
| 1126 | 1237 | /// - An argument of a .call node |
| 1127 | 1238 | /// - A parameter of a .fn_decl node |
| 1239 | /// - A value of a .struct_decl node | |
| 1128 | 1240 | pub fn argument(id: u32) Operand { |
| 1129 | return @enumFromInt(id + 5); | |
| 1241 | return @enumFromInt(id + 6); | |
| 1130 | 1242 | } |
| 1131 | 1243 | |
| 1132 | 1244 | fn getArgId(op: Operand) u32 { |
| 1133 | return @intFromEnum(op) - 5; | |
| 1245 | return @intFromEnum(op) - 6; | |
| 1246 | } | |
| 1247 | ||
| 1248 | pub fn format(op: Operand, comptime fmt: []const u8, _: std.fmt.FormatOptions, writer: std.io.AnyWriter) !void { | |
| 1249 | comptime assert(fmt.len == 0); | |
| 1250 | if (@intFromEnum(op) <= @intFromEnum(Operand.rhs)) { | |
| 1251 | try writer.writeAll("."); | |
| 1252 | try writer.writeAll(@tagName(op)); | |
| 1253 | } else { | |
| 1254 | try writer.print(".argument({d})", .{op.getArgId()}); | |
| 1255 | } | |
| 1134 | 1256 | } |
| 1135 | 1257 | }; |
| 1136 | 1258 | |
| 1137 | 1259 | /// Non-allocating formatter for taking an id token slice (raw), normalizing it |
| 1138 | 1260 | /// into it's canonical form. |
| 1139 | fn formatId(id: []const u8) std.fmt.Formatter(formatIdImpl) { | |
| 1261 | pub fn formatId(id: []const u8) std.fmt.Formatter(formatIdImpl) { | |
| 1140 | 1262 | return .{ .data = id }; |
| 1141 | 1263 | } |
| 1142 | 1264 | |
| ... | ... | @@ -1148,7 +1270,7 @@ fn formatIdImpl( |
| 1148 | 1270 | ) !void { |
| 1149 | 1271 | comptime assert(format.len == 0); |
| 1150 | 1272 | |
| 1151 | if (id[0] != '@') { | |
| 1273 | if (id[0] != '@' and !Tokenizer.Token.keywords.has(id)) { | |
| 1152 | 1274 | // no work needed |
| 1153 | 1275 | try writer.writeByte('"'); |
| 1154 | 1276 | try writer.writeAll(id); |
| ... | ... | @@ -1303,14 +1425,14 @@ fn formatIdImpl( |
| 1303 | 1425 | try writer.writeByte('"'); |
| 1304 | 1426 | } |
| 1305 | 1427 | |
| 1306 | fn formatIdCode(id: []const u8) std.fmt.Formatter(formatIdCodeImpl) { | |
| 1428 | pub fn formatIdCode(id: []const u8) std.fmt.Formatter(formatIdCodeImpl) { | |
| 1307 | 1429 | return .{ .data = id }; |
| 1308 | 1430 | } |
| 1309 | 1431 | |
| 1310 | 1432 | fn formatIdCodeImpl( |
| 1311 | 1433 | id: []const u8, |
| 1312 | 1434 | comptime format: []const u8, |
| 1313 | _: std.fmt.FormatOptions, | |
| 1435 | opts: std.fmt.FormatOptions, | |
| 1314 | 1436 | writer: anytype, |
| 1315 | 1437 | ) !void { |
| 1316 | 1438 | comptime assert(format.len == 0); |
| ... | ... | @@ -1319,10 +1441,7 @@ fn formatIdCodeImpl( |
| 1319 | 1441 | try writer.writeAll(id); |
| 1320 | 1442 | return; |
| 1321 | 1443 | } |
| 1322 | ||
| 1323 | try writer.writeAll("@\""); | |
| 1324 | ||
| 1325 | try writer.writeByte('"'); | |
| 1444 | try formatIdImpl(id, format, opts, writer); | |
| 1326 | 1445 | } |
| 1327 | 1446 | |
| 1328 | 1447 | const Report = @This(); |
src/core/Sema.zig+1341-1088| ... | ... | @@ -1,85 +1,287 @@ |
| 1 | /// absolutely stealing zig on this one haha | |
| 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) | |
| 26 | ||
| 27 | // inputs: | |
| 1 | //! Semantic Analysis | |
| 2 | //! | |
| 3 | //! Designed as a nonrecursive pause/resumable state machine, Sema converts | |
| 4 | //! declarations to Intern Pool `Value`s and function bodies to `Air`. | |
| 5 | //! | |
| 6 | //! - Compile time resolution of values | |
| 7 | //! - Type checking | |
| 8 | ||
| 28 | 9 | comp: *Comp, |
| 29 | gpa: Allocator, | |
| 30 | arena: Allocator, | |
| 31 | code: Teg, | |
| 32 | 10 | ip: *InternPool, |
| 33 | decl: *Decl, | |
| 34 | decl_index: Decl.Index, | |
| 35 | namespace_index: Namespace.Index, | |
| 11 | gpa: Allocator, | |
| 12 | arena_state: std.heap.ArenaAllocator, | |
| 13 | /// The thing being analyzed. | |
| 14 | /// When this is .decl, we are producing a `Value` | |
| 15 | owner: AnalysisUnit, | |
| 16 | ||
| 17 | /// The namespace index that is used for declaration lookups | |
| 18 | ns_index: Namespace.Index, | |
| 19 | /// Non-owning copy | |
| 20 | teg: Teg, | |
| 21 | /// The file that .teg is from | |
| 22 | file: *File, | |
| 23 | ||
| 24 | /// All errors and warnings from this decl | |
| 25 | report: Report.Builder, | |
| 26 | /// Map of `Teg` instructions to their resulting `Air` Ref | |
| 27 | inst_map: InstMap, | |
| 28 | ||
| 29 | /// `null` when not analyzing function bodies | |
| 30 | func: ?Function, | |
| 31 | ||
| 32 | frame: Frame, | |
| 33 | frame_stack: DynamicArray(Frame), | |
| 34 | ||
| 35 | /// List of `*Sema` that is waiting on this analysis to complete. | |
| 36 | /// Backed by `comp.gpa` | |
| 37 | blocked_semas: DynamicArray(*Sema), | |
| 36 | 38 | |
| 37 | // temp data: | |
| 38 | inst_map: InstMap = undefined, | |
| 39 | 39 | /// Scratch memory for temporary allocations. |
| 40 | 40 | /// Block contents are appended for marks. |
| 41 | scratch: core.U32Stack = .{}, | |
| 42 | ||
| 43 | /// This is only set for function declarations | |
| 44 | params: []Type = undefined, | |
| 41 | scratch: core.U32Stack, | |
| 45 | 42 | |
| 46 | // outputs | |
| 47 | report: Report.Builder = undefined, | |
| 48 | air_instructions: std.MultiArrayList(Air.Inst) = .{}, | |
| 49 | air_extra: store.ExtraStore(.air, .@"var") = .{}, | |
| 43 | /// To validate that a pointer to `Sema` is not corrupted. | |
| 44 | magic: Magic align(1), | |
| 50 | 45 | |
| 51 | temp_result_map: std.AutoArrayHashMapUnmanaged(Teg.Inst.Index, Type) = .{}, | |
| 46 | instruction_step_lock: std.debug.SafetyLock, | |
| 52 | 47 | |
| 53 | 48 | pub const Error = error{ |
| 54 | /// Analysis has failed, see error list | |
| 49 | /// The analysis of this branch or declaration has failed. | |
| 50 | /// At least one error will be placed in the `Sema`'s report | |
| 55 | 51 | AnalysisFail, |
| 56 | // This analysis is waiting on other dependencies to finish | |
| 57 | // Interrupt, | |
| 58 | ||
| 59 | // TODO: add handling of this | |
| 52 | /// There is a dependency on another declaration that we are waiting on. | |
| 53 | Interrupt, | |
| 54 | /// Must invoke garbage collection? | |
| 60 | 55 | TooManyValues, |
| 61 | // TODO: add handling of this | |
| 62 | HasNulls, | |
| 63 | // TODO: remove this from the primary error set | |
| 64 | HitNoReturn, | |
| 65 | ||
| 66 | OutStream, | |
| 67 | 56 | OutOfMemory, |
| 68 | } || Comp.File.GetTegError; | |
| 57 | }; | |
| 58 | ||
| 59 | pub fn init( | |
| 60 | s: *Sema, | |
| 61 | comp: *Comp, | |
| 62 | file: *File, | |
| 63 | teg: Teg, | |
| 64 | owner: AnalysisUnit, | |
| 65 | ns_index: Namespace.Index, | |
| 66 | arena_state: std.heap.ArenaAllocator, | |
| 67 | ) Allocator.Error!void { | |
| 68 | const t = tracer.trace(@src()); | |
| 69 | defer t.end(); | |
| 70 | ||
| 71 | assert(comp.intern_pool.items.len > 0); // did not initialize intern pool | |
| 72 | _ = comp.intern_pool.items.bytes[0]; // segfault if intern pool is undefined memory | |
| 73 | ||
| 74 | s.* = .{ | |
| 75 | .comp = comp, | |
| 76 | .ip = &comp.intern_pool, | |
| 77 | .gpa = comp.gpa, | |
| 78 | ||
| 79 | .arena_state = arena_state, | |
| 80 | .owner = owner, | |
| 81 | .ns_index = ns_index, | |
| 82 | .teg = teg, | |
| 83 | .file = file, | |
| 84 | ||
| 85 | .report = .init(comp.gpa), | |
| 86 | .inst_map = undefined, | |
| 87 | .func = switch (owner.kind) { | |
| 88 | .decl => null, | |
| 89 | .func => .empty, | |
| 90 | }, | |
| 91 | .frame = undefined, | |
| 92 | .frame_stack = .empty, | |
| 93 | .blocked_semas = .empty, | |
| 94 | .scratch = .empty, | |
| 95 | .magic = .valid, | |
| 96 | .instruction_step_lock = .{}, | |
| 97 | }; | |
| 98 | ||
| 99 | const first_instruction, const count = switch (owner.unwrap()) { | |
| 100 | .decl => |decl| data: { | |
| 101 | const decl_inst = s.ip.declPtr(decl).teg_decl_index.resolve(s.ip); | |
| 102 | const teg_decl: Teg.Decl = teg.instPayload(.declaration, decl_inst); | |
| 103 | break :data .{ decl_inst, teg_decl.instruction_count }; | |
| 104 | }, | |
| 105 | .func => |value| data: { | |
| 106 | const func_inst = value.toKey(s.ip).func.teg_body_inst.get(s.ip); | |
| 107 | const func: Teg.Func = teg.instPayload(.func, func_inst.inst); | |
| 108 | break :data .{ func.body, func.instruction_count }; | |
| 109 | }, | |
| 110 | }; | |
| 111 | ||
| 112 | s.inst_map = try .init(s.arena(), first_instruction, count); | |
| 113 | ||
| 114 | const block_index = switch (owner.unwrap()) { | |
| 115 | .decl => switch (s.teg.declData(first_instruction).body.decode()) { | |
| 116 | .index => |index| index, | |
| 117 | .value => unreachable, // Sema must not be created for a static value | |
| 118 | }, | |
| 119 | .func => first_instruction, | |
| 120 | }; | |
| 121 | s.frame = Frame.initFromBlock( | |
| 122 | s, | |
| 123 | block_index, | |
| 124 | switch (owner.kind) { | |
| 125 | .decl => .compile_time, | |
| 126 | .func => .run_time, | |
| 127 | }, | |
| 128 | .none, | |
| 129 | ); | |
| 130 | ||
| 131 | if (owner.kind == .func) { | |
| 132 | assert(try s.addIndexUntracked(.block, undefined) == .main_block); | |
| 133 | } | |
| 134 | } | |
| 135 | ||
| 136 | /// Does not free the allocated pointer itself. Releases the arena to be used for another analysis. | |
| 137 | pub fn deinit(sema: *Sema, arena_reset_mode: std.heap.ArenaAllocator.ResetMode) std.heap.ArenaAllocator { | |
| 138 | defer sema.* = undefined; | |
| 139 | assert(sema.magic == .valid); | |
| 140 | ||
| 141 | sema.report.deinit(); | |
| 142 | sema.blocked_semas.deinit(sema.gpa); | |
| 143 | ||
| 144 | if (sema.func) |*func| { | |
| 145 | func.deinit(sema.gpa); | |
| 146 | } | |
| 147 | ||
| 148 | _ = sema.arena_state.reset(arena_reset_mode); | |
| 149 | return sema.arena_state; | |
| 150 | } | |
| 151 | ||
| 152 | const AnalysisMode = enum { | |
| 153 | /// Runtime operands are allowed, and emit Air instructions on `sema.func`. | |
| 154 | run_time, | |
| 155 | /// Any attempt to emit instructions turn into analysis errors, the result | |
| 156 | /// is guanteed to be a `Value`. | |
| 157 | /// | |
| 158 | /// Compile time analysis can result in a function who's body executes at | |
| 159 | /// run time, but that body is not queued for analysis until .run_time | |
| 160 | /// analysis requires the function. | |
| 161 | compile_time, | |
| 162 | }; | |
| 163 | ||
| 164 | const Frame = struct { | |
| 165 | ip: [*]const Teg.Inst.Index, | |
| 166 | instructions_left: if (runtime_safety) usize else void, | |
| 167 | ||
| 168 | mode: AnalysisMode, | |
| 169 | block_index: Teg.Inst.Index, | |
| 170 | mark_index: u32, | |
| 171 | data: Data, | |
| 172 | ||
| 173 | const Data = union { | |
| 174 | pub const none = .{ .undefined = {} }; | |
| 175 | ||
| 176 | undefined: void, | |
| 177 | conditional: Conditional, | |
| 178 | }; | |
| 179 | ||
| 180 | const Conditional = enum { | |
| 181 | comptime_condition, | |
| 182 | runtime_yes, | |
| 183 | runtime_no, | |
| 184 | }; | |
| 185 | ||
| 186 | fn initFromBlock(s: *Sema, block_index: Teg.Inst.Index, mode: AnalysisMode, data: Data) Frame { | |
| 187 | const list = s.teg.instEncodedData(block_index) | |
| 188 | .decodeUnsafe(Teg.PlNode(Teg.IndexList), s.teg.instTag(block_index)) | |
| 189 | .extra_index.get(&s.teg) | |
| 190 | .slice(&s.teg); | |
| 191 | return .{ | |
| 192 | .ip = list[0..].ptr, | |
| 193 | .instructions_left = if (runtime_safety) list.len, | |
| 194 | .block_index = block_index, | |
| 195 | .data = data, | |
| 196 | .mode = mode, | |
| 197 | .mark_index = @intCast(s.scratch.mark().index), | |
| 198 | }; | |
| 199 | } | |
| 69 | 200 | |
| 70 | /// Instead of using a Hash map for this, all of the information is pre-allocated. | |
| 71 | /// This is only possible because all instructions are adjacent in Teg. | |
| 201 | fn currentIndex(frame: *const Frame) Teg.Inst.Index { | |
| 202 | // validate the pointer is correct | |
| 203 | if (runtime_safety) { | |
| 204 | const s: *const Sema = @fieldParentPtr("frame", frame); | |
| 205 | assert(s.magic == .valid); | |
| 206 | const extra = s.teg.extra.list.items; | |
| 207 | assert(@intFromPtr(frame.ip) > @intFromPtr(extra.ptr) and | |
| 208 | @intFromPtr(frame.ip) <= @intFromPtr(&extra.ptr[extra.len - 1])); | |
| 209 | } | |
| 210 | ||
| 211 | return frame.ip[0]; | |
| 212 | } | |
| 213 | ||
| 214 | fn currentTag(frame: *const Frame) Teg.Tag { | |
| 215 | const s: *const Sema = @fieldParentPtr("frame", frame); | |
| 216 | assert(s.magic == .valid); | |
| 217 | return s.teg.instTag(frame.currentIndex()); | |
| 218 | } | |
| 219 | ||
| 220 | fn currentData(frame: *const Frame, comptime tag: Teg.Tag) tag.Data() { | |
| 221 | const s: *const Sema = @fieldParentPtr("frame", frame); | |
| 222 | assert(s.magic == .valid); | |
| 223 | return s.teg.instData(tag, frame.currentIndex()); | |
| 224 | } | |
| 225 | ||
| 226 | fn currentPayload(frame: *const Frame, comptime tag: Teg.Tag) tag.Data().T { | |
| 227 | const s: *const Sema = @fieldParentPtr("frame", frame); | |
| 228 | assert(s.magic == .valid); | |
| 229 | return s.teg.instPayload(tag, frame.currentIndex()); | |
| 230 | } | |
| 231 | ||
| 232 | fn advance(frame: *Frame) void { | |
| 233 | if (runtime_safety) { | |
| 234 | frame.instructions_left -= 1; | |
| 235 | assert(frame.instructions_left > 0); // block is missing a break/did not stop at break | |
| 236 | } | |
| 237 | frame.ip += 1; | |
| 238 | } | |
| 239 | }; | |
| 240 | ||
| 241 | const Function = struct { | |
| 242 | instructions: std.MultiArrayList(Air.Inst), | |
| 243 | extra: store.ExtraStore(.air, .@"var"), | |
| 244 | ||
| 245 | pub const empty: Function = .{ | |
| 246 | .instructions = .{}, | |
| 247 | .extra = .empty, | |
| 248 | }; | |
| 249 | ||
| 250 | pub fn finalize(func: *Function, gpa: Allocator) !Air { | |
| 251 | defer func.* = .empty; | |
| 252 | const air: Air = .{ | |
| 253 | .insts = func.instructions.slice(), | |
| 254 | .extra = try func.extra.toOwnedStore(gpa), | |
| 255 | }; | |
| 256 | return air; | |
| 257 | } | |
| 258 | ||
| 259 | pub fn deinit(func: *Function, gpa: Allocator) void { | |
| 260 | defer func.* = undefined; | |
| 261 | func.instructions.deinit(gpa); | |
| 262 | func.extra.deinit(gpa); | |
| 263 | } | |
| 264 | }; | |
| 265 | ||
| 266 | /// Instead of using a HashMap, all of the information is pre-allocated, and | |
| 267 | /// lookups are done by subtracing the base index. This is only possible because | |
| 268 | /// all instructions are adjacent, and a declaration knows exactly how many | |
| 269 | /// instructions it has. | |
| 72 | 270 | const InstMap = struct { |
| 73 | 271 | items: []Air.Inst.Ref.Optional, |
| 74 | 272 | start: Teg.Inst.Index, |
| 75 | debug_traces: if (std.debug.runtime_safety) []StoredTrace else void, | |
| 76 | ||
| 77 | pub fn init(allocator: Allocator, code: *const Teg, inst_start: Teg.Inst.Index) !InstMap { | |
| 78 | const decl = code.declData(inst_start); | |
| 79 | const insts = try allocator.alloc(Air.Inst.Ref.Optional, decl.instruction_count); | |
| 80 | const debug_traces = if (std.debug.runtime_safety) | |
| 81 | try allocator.alloc(StoredTrace, decl.instruction_count); | |
| 82 | @memset(insts, .none); | |
| 273 | debug_traces: if (runtime_safety) []StoredTrace else void, | |
| 274 | ||
| 275 | pub fn init(allocator: Allocator, inst_start: Teg.Inst.Index, inst_len: u32) !InstMap { | |
| 276 | const insts = try allocator.alloc(Air.Inst.Ref.Optional, inst_len); | |
| 277 | errdefer allocator.free(insts); | |
| 278 | ||
| 279 | const debug_traces = if (runtime_safety) | |
| 280 | try allocator.alloc(StoredTrace, inst_len); | |
| 281 | errdefer if (runtime_safety) | |
| 282 | allocator.free(debug_traces); | |
| 283 | ||
| 284 | @memset(insts[0..], .none); | |
| 83 | 285 | return .{ |
| 84 | 286 | .items = insts, |
| 85 | 287 | .start = inst_start, |
| ... | ... | @@ -89,435 +291,262 @@ const InstMap = struct { |
| 89 | 291 | |
| 90 | 292 | pub fn deinit(map: *InstMap, allocator: Allocator) void { |
| 91 | 293 | allocator.free(map.items); |
| 92 | if (std.debug.runtime_safety) | |
| 294 | if (runtime_safety) | |
| 93 | 295 | allocator.free(map.debug_traces); |
| 94 | 296 | map.* = undefined; |
| 95 | 297 | } |
| 96 | 298 | |
| 97 | pub fn get(map: *const InstMap, teg_code: Teg.Inst.Index) ?Air.Inst.Ref { | |
| 299 | pub fn getOrNull(map: *const InstMap, teg_code: Teg.Inst.Index) ?Air.Inst.Ref { | |
| 98 | 300 | const source_i = @intFromEnum(teg_code); |
| 99 | 301 | const start_i = @intFromEnum(map.start); |
| 100 | assert(source_i >= start_i and source_i <= (start_i + map.items.len)); | |
| 302 | assert(source_i >= start_i and source_i <= start_i + map.items.len); | |
| 303 | if (assertFailed(source_i >= start_i and source_i <= start_i + map.items.len)) { | |
| 304 | std.debug.panic("attempt fetching instruction %{d} which is out of range of %{d}-%{d}", .{ | |
| 305 | source_i, | |
| 306 | start_i, | |
| 307 | start_i + map.items.len, | |
| 308 | }); | |
| 309 | } | |
| 101 | 310 | return map.items[source_i - start_i].unwrap(); |
| 102 | 311 | } |
| 103 | 312 | |
| 104 | pub fn add(map: *InstMap, from: Teg.Inst.Index, to: Air.Inst.Ref) !void { | |
| 313 | pub fn get(map: *const InstMap, teg_code: Teg.Inst.Index) Air.Inst.Ref { | |
| 314 | return map.getOrNull(teg_code) orelse { | |
| 315 | if (runtime_safety) { | |
| 316 | std.debug.panic("did not analyze %{d}", .{@intFromEnum(teg_code)}); | |
| 317 | } | |
| 318 | unreachable; // did not analyze index | |
| 319 | }; | |
| 320 | } | |
| 321 | ||
| 322 | pub fn add(map: *InstMap, from: Teg.Inst.Index, to: Air.Inst.Ref) void { | |
| 105 | 323 | const source_i = @intFromEnum(from); |
| 106 | 324 | const start_i = @intFromEnum(map.start); |
| 325 | if (assertFailed(source_i >= start_i and source_i <= start_i + map.items.len)) { | |
| 326 | std.debug.panic("attempt mapping instruction %{d} which is out of range of %{d}-%{d}", .{ | |
| 327 | source_i, | |
| 328 | start_i, | |
| 329 | start_i + map.items.len, | |
| 330 | }); | |
| 331 | } | |
| 107 | 332 | const index = source_i - start_i; |
| 108 | if (assertFailed(map.get(from) == null)) { | |
| 333 | if (assertFailed(map.getOrNull(from) == null)) { | |
| 109 | 334 | const s: *const Sema = @fieldParentPtr("inst_map", map); |
| 110 | 335 | std.debug.print("Original was added here:\n", .{}); |
| 111 | 336 | std.debug.dumpStackTrace(map.debug_traces[index].trace()); |
| 112 | 337 | std.debug.panic("%{d} is already mapped (to {})", .{ |
| 113 | 338 | @intFromEnum(from), |
| 114 | map.get(from).?.dump(s.ip), | |
| 339 | map.get(from).dump(s.ip), | |
| 115 | 340 | }); |
| 116 | 341 | } |
| 117 | 342 | map.items[index] = to.toOptional(); |
| 118 | if (std.debug.runtime_safety) | |
| 343 | if (runtime_safety) | |
| 119 | 344 | map.debug_traces[index] = StoredTrace.capture(@returnAddress()); |
| 120 | 345 | } |
| 121 | 346 | }; |
| 122 | 347 | |
| 123 | pub fn init(s: *Sema) !void { | |
| 124 | const t = tracer.trace(@src()); | |
| 125 | defer t.end(); | |
| 126 | ||
| 127 | assert(s.ip.items.len > 0); // did not initialize intern pool | |
| 128 | _ = s.ip.items.bytes[0]; // segfault if intern pool is undefined memory | |
| 129 | ||
| 130 | s.report = .{ | |
| 131 | .gpa = s.gpa, | |
| 132 | .current_file = s.ip.namespacePtr(s.namespace_index).file_scope, | |
| 133 | }; | |
| 134 | ||
| 135 | s.inst_map = try InstMap.init( | |
| 136 | s.arena, | |
| 137 | &s.code, | |
| 138 | s.ip.declPtr(s.decl_index).teg_decl_index.resolve(s.ip), | |
| 139 | ); | |
| 140 | } | |
| 141 | ||
| 142 | pub fn deinit(s: *Sema) void { | |
| 143 | const t = tracer.trace(@src()); | |
| 144 | defer t.end(); | |
| 145 | ||
| 146 | const report = s.report.finalize() catch @panic("OOM"); | |
| 147 | if ((report.errors.len + report.warnings.len) > 0) { | |
| 148 | s.comp.temp_flag_had_errors = true; | |
| 149 | } | |
| 150 | report.dumpToWriter(s.comp, s.comp.out.writer, s.comp.out.tty) catch @panic("Error"); | |
| 151 | report.deinit(s.gpa); | |
| 152 | ||
| 153 | defer s.* = undefined; | |
| 154 | s.inst_map.deinit(s.arena); | |
| 155 | s.scratch.list.deinit(s.arena); | |
| 156 | s.air_extra.deinit(s.gpa); | |
| 157 | s.air_instructions.deinit(s.gpa); | |
| 158 | s.temp_result_map.deinit(s.arena); | |
| 348 | fn markInst(s: *Sema, inst: Air.Inst.Index) void { | |
| 349 | const flags: *Air.Flags = &s.func.?.instructions.items(.flags)[@intFromEnum(inst)]; | |
| 350 | flags.uses = flags.uses.increment(); | |
| 159 | 351 | } |
| 160 | 352 | |
| 161 | pub fn finalize(s: *Sema) !void { | |
| 162 | s.decl.analysis = .complete; | |
| 353 | // TODO: name when awaake pls | |
| 354 | const DoMark = enum { | |
| 355 | no_mark, | |
| 356 | /// A Ref must be marked if it's value is used as the operand for another | |
| 357 | /// Air instruction. This informs the Js backend if an expression must | |
| 358 | /// generate a temporary variable. | |
| 359 | marked, | |
| 360 | }; | |
| 163 | 361 | |
| 164 | const air: Air = s.tempAir(); | |
| 362 | fn getRef(s: *Sema, teg_ref: Teg.Inst.Ref, mark: DoMark) Air.Inst.Ref { | |
| 363 | switch (teg_ref.decode()) { | |
| 364 | .value => |value| return value.toAir(), | |
| 365 | .index => |index| { | |
| 366 | const air = s.inst_map.get(index); | |
| 165 | 367 | |
| 166 | if (s.decl.value().valueTag(s.ip) == .func) { | |
| 167 | if (s.comp.options.dump_ir) { | |
| 168 | try s.comp.out.print("=" ** 20, .{}); | |
| 169 | try s.comp.out.print("\nAir for {}\n", .{s.decl.value().dump(s.ip)}); | |
| 170 | try air.dumpToWriter(s.ip, s.comp.out); | |
| 171 | } | |
| 368 | if (mark == .marked) { | |
| 369 | switch (air.decode()) { | |
| 370 | .index => |i| s.markInst(i), | |
| 371 | else => {}, | |
| 372 | } | |
| 373 | } | |
| 172 | 374 | |
| 173 | try s.comp.codeGenDecl(s.decl.value(), &air); | |
| 375 | return air; | |
| 376 | }, | |
| 174 | 377 | } |
| 175 | 378 | } |
| 176 | 379 | |
| 177 | // --- analyze functions --- | |
| 178 | ||
| 179 | /// This is the entrypoint to semantic analysis: It all starts at a declaration. | |
| 180 | pub fn analyzeDeclaration(s: *Sema) Error!Value { | |
| 181 | const decl = s.code.declData(s.inst_map.start); | |
| 182 | switch (decl.kind) { | |
| 183 | .function => return analyzeFunctionDeclaration(s), | |
| 184 | .immutable => {}, | |
| 185 | } | |
| 186 | ||
| 187 | const root_block = decl.body; | |
| 188 | switch (root_block.decode()) { | |
| 189 | .value => |val| { | |
| 190 | s.decl.raw_value = val; | |
| 191 | s.decl.analysis = .complete; | |
| 192 | }, | |
| 380 | fn getValue(s: *Sema, src: LazyNode, teg_ref: Teg.Inst.Ref) !Value { | |
| 381 | switch (teg_ref.decode()) { | |
| 382 | .value => |value| return value, | |
| 193 | 383 | .index => |index| { |
| 194 | const result = try s.analyzeBlock(index); | |
| 195 | switch (result.decode()) { | |
| 196 | .value => |val| { | |
| 197 | s.decl.raw_value = val; | |
| 198 | s.decl.analysis = .complete; | |
| 199 | }, | |
| 200 | .index => @panic("TODO"), | |
| 201 | } | |
| 384 | return s.inst_map.get(index).value() orelse | |
| 385 | return s.fail(.not_comptime_known, .{ .src = src }); | |
| 202 | 386 | }, |
| 203 | 387 | } |
| 204 | ||
| 205 | return s.decl.value(); | |
| 206 | 388 | } |
| 207 | 389 | |
| 208 | /// TODO: Delete this legacy code. This will be replaced by analyzeDeclaration and a lambda | |
| 209 | /// function (See #45) | |
| 210 | pub fn analyzeFunctionDeclaration(s: *Sema) Error!Value { | |
| 211 | const t = tracer.trace(@src()); | |
| 212 | defer t.end(); | |
| 213 | ||
| 214 | // TODO: turn this entire function into a nonrecursive state machine, so | |
| 215 | // that analysis can pause and continue at any point. for example, when a | |
| 216 | // dependency on another declaration is required, but that declaration has | |
| 217 | // not been resolved yet. or, we are waiting on a file read to happen | |
| 218 | ||
| 219 | const decl, const fn_decl = s.code.fullFuncDecl(s.inst_map.start); | |
| 220 | assert(decl.kind == .function); | |
| 390 | fn getCoercedValue(s: *Sema, src: LazyNode, teg_ref: Teg.Inst.Ref, ty: Type) !Value { | |
| 391 | const any = try s.getValue(src, teg_ref); | |
| 392 | return try s.coerceValue(src, any, ty); | |
| 393 | } | |
| 221 | 394 | |
| 222 | s.params = try s.gpa.alloc(Type, fn_decl.params.len); | |
| 223 | defer s.gpa.free(s.params); | |
| 395 | fn getCoercedRef(s: *Sema, src: LazyNode, teg_ref: Teg.Inst.Ref, ty: Type, mark: DoMark) !Air.Inst.Ref { | |
| 396 | const any = s.getRef(teg_ref, mark); | |
| 397 | return try s.coerceRef(src, any, ty); | |
| 398 | } | |
| 224 | 399 | |
| 225 | for (fn_decl.params.slice(&s.code), s.params, 0..) |param, *dest, i| { | |
| 226 | const src = s.inst_map.start.op(operandArgument(@intCast(i))); | |
| 227 | const ty = try s.resolveType(src, param); | |
| 228 | if (!ty.hasPossibleRepresentation(s.ip)) { | |
| 229 | return s.fail(.param_comptime_only, .{ | |
| 230 | .src = s.lazyNode(src), | |
| 231 | .type = ty, | |
| 232 | }); | |
| 233 | } | |
| 234 | dest.* = ty; | |
| 235 | } | |
| 400 | fn getType(s: *Sema, src: LazyNode, teg_ref: Teg.Inst.Ref) !Type { | |
| 401 | return (try s.getCoercedValue(src, teg_ref, .type)).toTypeUnsafe(s.ip); | |
| 402 | } | |
| 236 | 403 | |
| 237 | var return_type, const early_value = if (fn_decl.return_type.unwrap()) |ty| brk: { | |
| 238 | const return_type = try s.resolveType( | |
| 239 | s.inst_map.start.op(.return_type), | |
| 240 | ty, | |
| 241 | ); | |
| 242 | const value = try s.makeValue(.{ | |
| 243 | .func = .{ | |
| 244 | .ty = try s.makeType(.{ | |
| 245 | .function_type = .{ | |
| 246 | .param_types = s.params, | |
| 247 | .return_type = return_type, | |
| 248 | }, | |
| 249 | }), | |
| 250 | .owner_decl = s.decl_index, | |
| 251 | .teg_body_inst = s.decl.teg_decl_index, | |
| 404 | pub fn run(s: *Sema) Error!void { | |
| 405 | while (true) if (try s.step()) |result| { | |
| 406 | switch (s.owner.unwrap()) { | |
| 407 | .decl => |decl_index| { | |
| 408 | const decl = s.ip.declPtr(decl_index); | |
| 409 | decl.raw_value = result; | |
| 410 | decl.analysis = .complete; | |
| 252 | 411 | }, |
| 253 | }); | |
| 254 | ||
| 255 | s.decl.raw_value = value.typeOf(s.ip).value(); | |
| 256 | s.decl.analysis = .in_progress_type_available; | |
| 257 | ||
| 258 | break :brk .{ return_type, value }; | |
| 259 | } else .{ null, null }; | |
| 260 | ||
| 261 | const root_block = decl.body; | |
| 262 | switch (root_block.decode()) { | |
| 263 | .value => |val| { | |
| 264 | const coerced = if (return_type) |ty| | |
| 265 | try s.coerceValue( | |
| 266 | s.inst_map.start.op(.operand), | |
| 267 | val, | |
| 268 | ty, | |
| 269 | ) | |
| 270 | else brk: { | |
| 271 | return_type = val.typeOf(s.ip); | |
| 272 | break :brk val; | |
| 273 | }; | |
| 412 | .func => |value| { | |
| 413 | const air = try s.func.?.finalize(s.gpa); | |
| 414 | try s.comp.jobs.writeItem(.{ .codegen_func = .{ | |
| 415 | .value = value, | |
| 416 | .air = air, | |
| 417 | } }); | |
| 418 | }, | |
| 419 | } | |
| 420 | return; | |
| 421 | }; | |
| 422 | } | |
| 274 | 423 | |
| 275 | const block = try s.addBlock(); | |
| 276 | _ = try s.addInstRef(.@"break", .{ | |
| 277 | .block_ref = block.block_index, | |
| 278 | .value = coerced.toAir(), | |
| 279 | }); | |
| 280 | }, | |
| 281 | .index => |index| { | |
| 282 | if (return_type) |ty| { | |
| 283 | try s.temp_result_map.putNoClobber(s.arena, index, ty); | |
| 284 | } | |
| 424 | pub fn step(s: *Sema) Error!?Value { | |
| 425 | s.instruction_step_lock.lock(); // Semantic analysis must not be recursive | |
| 426 | defer s.instruction_step_lock.unlock(); | |
| 285 | 427 | |
| 286 | const out = try s.analyzeBlock(index); | |
| 428 | s.debug("step %{d}", .{@intFromEnum(s.frame.currentIndex())}); | |
| 287 | 429 | |
| 288 | return_type = return_type orelse s.typeOfRef(out); | |
| 289 | }, | |
| 290 | } | |
| 430 | switch (s.frame.currentTag()) { | |
| 431 | inline else => |tag| { | |
| 432 | const start_index = s.frame.currentIndex(); | |
| 433 | const result = try s.stepInner(tag); | |
| 434 | switch (InstructionKind.fromTag(tag)) { | |
| 435 | .result => { | |
| 436 | s.debug("-> {}", .{result.dump(s.ip)}); | |
| 291 | 437 | |
| 292 | const value = early_value orelse try s.makeValue(.{ | |
| 293 | .func = .{ | |
| 294 | .ty = try s.makeType(.{ | |
| 295 | .function_type = .{ | |
| 296 | .param_types = s.params, | |
| 297 | .return_type = return_type orelse | |
| 298 | unreachable, // type is reassigned above | |
| 438 | s.inst_map.add(s.frame.ip[0], result); | |
| 439 | s.frame.advance(); | |
| 440 | }, | |
| 441 | .void => { | |
| 442 | s.frame.advance(); | |
| 443 | }, | |
| 444 | .control_flow => { | |
| 445 | // the instruction pointer has been moved already | |
| 446 | assert(s.frame.currentIndex() != start_index); // control flow loop | |
| 447 | }, | |
| 448 | .pop_control_flow => switch (result) { | |
| 449 | .@"continue" => { | |
| 450 | // the instruction pointer has been moved already | |
| 451 | assert(s.frame.currentIndex() != start_index); // control flow loop | |
| 452 | }, | |
| 453 | .stop => return switch (s.owner.kind) { | |
| 454 | .decl => s.inst_map.get(s.frame.block_index).decode().value, | |
| 455 | .func => .void, | |
| 456 | }, | |
| 299 | 457 | }, |
| 300 | }), | |
| 301 | .owner_decl = s.decl_index, | |
| 302 | .teg_body_inst = s.decl.teg_decl_index, | |
| 458 | } | |
| 303 | 459 | }, |
| 304 | }); | |
| 305 | ||
| 306 | s.decl.analysis = .complete; | |
| 307 | s.decl.raw_value = value; | |
| 460 | } | |
| 308 | 461 | |
| 309 | return value; | |
| 462 | return null; | |
| 310 | 463 | } |
| 311 | 464 | |
| 312 | pub fn analyzeBlock(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { | |
| 313 | const t = tracer.trace(@src()); | |
| 314 | defer t.end(); | |
| 315 | ||
| 316 | const scratch = s.scratch.mark(); | |
| 317 | defer scratch.deinit(); | |
| 318 | ||
| 319 | const block = try s.addBlock(); | |
| 320 | try s.inst_map.add(in, block.block_index.toRef()); | |
| 321 | ||
| 322 | var last: Air.Inst.Ref = Value.void.toAir(); | |
| 323 | const instructions = s.code.blockInstructions(in); | |
| 324 | assert(instructions.len != 0); | |
| 325 | for (instructions) |inst| { | |
| 326 | last = s.analyzeCode(inst) catch |err| switch (err) { | |
| 327 | error.HitNoReturn => break, | |
| 328 | else => return err, | |
| 465 | const InstructionKind = enum { | |
| 466 | result, | |
| 467 | void, | |
| 468 | control_flow, | |
| 469 | pop_control_flow, | |
| 470 | ||
| 471 | pub inline fn fromTag(tag: Teg.Tag) InstructionKind { | |
| 472 | return switch (tag) { | |
| 473 | // these push new frames, and frame end will assign its value | |
| 474 | .func, | |
| 475 | .block, | |
| 476 | .@"comptime", | |
| 477 | .@"if", | |
| 478 | => .control_flow, | |
| 479 | // these pop frames, which may end the compilation | |
| 480 | .@"break", | |
| 481 | => .pop_control_flow, | |
| 482 | // these are statements which do not evaluate to anything (void) | |
| 483 | .discard, | |
| 484 | .ensure_void, | |
| 485 | => .void, | |
| 486 | // all other instructions are single-visit, returning a value | |
| 487 | else => .result, | |
| 329 | 488 | }; |
| 330 | 489 | } |
| 331 | 490 | |
| 332 | if (scratch.len() == 1) { | |
| 333 | const air_instructions = s.air_instructions.slice(); | |
| 334 | const single_inst_id = @intFromEnum(block.block_index) + 1; | |
| 335 | assert(@intFromEnum(scratch.slice(Air.Inst.Index)[0]) == single_inst_id); | |
| 336 | ||
| 337 | const single_instruction = air_instructions.items(.tag)[single_inst_id]; | |
| 338 | if (single_instruction == .@"break") { | |
| 339 | const data: Air.Break = air_instructions.items(.data)[single_inst_id] | |
| 340 | .decode(.@"break", single_instruction); | |
| 341 | if (data.block_ref == block.block_index) { | |
| 342 | // This is a block that resolved to just a single value ref with | |
| 343 | // no side effects. This means we can just lower this to the ref | |
| 344 | // itself, and give codegen an easier time. | |
| 345 | ||
| 346 | // Delete the block and the single instruction | |
| 347 | // Break does not use any extra data, so this call removes both items | |
| 348 | s.air_instructions.shrinkRetainingCapacity(@intFromEnum(block.block_index)); | |
| 349 | ||
| 350 | return data.value; | |
| 351 | } | |
| 352 | } | |
| 353 | } | |
| 354 | ||
| 355 | return (try block.finish(s, scratch)).toRef(); | |
| 356 | } | |
| 357 | ||
| 358 | fn analyzeCode(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { | |
| 359 | const t = tracer.traceFmt(@src(), "%{d}, {s}", .{ @intFromEnum(in), @tagName(s.code.instTag(in)) }); | |
| 360 | defer t.end(); | |
| 361 | ||
| 362 | const out = try s.analyzeCodeInner(in); | |
| 363 | ||
| 364 | // Block refs are added automatically | |
| 365 | if (s.code.instTag(in) == .block) { | |
| 366 | assert(s.inst_map.get(in) != null); | |
| 367 | } else { | |
| 368 | try s.inst_map.add(in, out); | |
| 491 | pub fn Result(kind: InstructionKind) type { | |
| 492 | return switch (kind) { | |
| 493 | .result => Air.Inst.Ref, | |
| 494 | .void, .control_flow => void, | |
| 495 | .pop_control_flow => PopControlFlowState, | |
| 496 | }; | |
| 369 | 497 | } |
| 498 | }; | |
| 370 | 499 | |
| 371 | return out; | |
| 372 | } | |
| 500 | const PopControlFlowState = enum { stop, @"continue" }; | |
| 373 | 501 | |
| 374 | fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { | |
| 375 | switch (s.code.instTag(in)) { | |
| 502 | fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag).Result() { | |
| 503 | switch (tag) { | |
| 504 | // TODO: pre-evaluate `.param` nodes, since they are always at the start of a function. | |
| 505 | // this way we can avoid repeating the non-trivial call into InternPool | |
| 506 | .param => { | |
| 507 | const param: Teg.DataNode = s.frame.currentData(.param); | |
| 508 | assert(s.frame.mode == .run_time); // never emitted in a comptime block | |
| 509 | assert(s.owner.kind == .func); // only emitted in function body | |
| 510 | const param_types = s.owner.unwrap().func.toKey(s.ip).func.ty.toFunctionPrototype(s.ip).?.param_types; | |
| 511 | return s.addRef(.arg, .{ | |
| 512 | .type = param_types[param.data], | |
| 513 | .arg_number = param.data, | |
| 514 | }); | |
| 515 | }, | |
| 376 | 516 | .string => { |
| 377 | const string: Teg.SlicedString = s.code.instPayload(.string, in); | |
| 378 | return s.makeValueAir(.{ .string = string.slice(s.code.string_bytes) }); | |
| 517 | const string: Teg.SlicedString = s.frame.currentPayload(.string); | |
| 518 | return s.makeValueAir(.{ .string = string.slice(s.teg.string_bytes) }); | |
| 379 | 519 | }, |
| 380 | 520 | .number => { |
| 381 | const number: store.Float = s.code.instPayload(.number, in); | |
| 521 | const number: store.Float = s.frame.currentPayload(.number); | |
| 382 | 522 | return s.makeValueAir(.{ .comptime_float = number.decode() }); |
| 383 | 523 | }, |
| 384 | .js => { | |
| 385 | const operand_ref = s.code.instData(.js, in).operand; | |
| 386 | const operand = try s.resolveCoerceValue(in.op(.operand), operand_ref, .string); | |
| 387 | return s.addInstRef(.js_global_ref, operand.asString(s.ip)); | |
| 388 | }, | |
| 389 | .field => { | |
| 390 | const field: Teg.Bin = s.code.instPayload(.field, in); | |
| 391 | const base = try s.resolveRef(field.lhs); | |
| 392 | const ty = s.typeOfRef(base); | |
| 393 | ||
| 394 | const rhs_raw = try s.resolveRef(field.rhs); | |
| 395 | ||
| 396 | if (ty.toArrayType(s.ip)) |_| { | |
| 397 | // Special case `.len` | |
| 398 | get_array_len: { | |
| 399 | const value = rhs_raw.value() orelse break :get_array_len; | |
| 400 | if (value.valueTag(s.ip) != .string) break :get_array_len; | |
| 401 | const string = value.asString(s.ip).toSlice(s.ip); | |
| 402 | if (!std.mem.eql(u8, string, "len")) break :get_array_len; | |
| 403 | ||
| 404 | switch (base.decode()) { | |
| 405 | .value => |v| { | |
| 406 | const items_len = v.valueData(s.ip, .array_value).get(s.ip).items_len; | |
| 407 | return s.makeValueAir(.{ .int32 = @intCast(items_len) }); | |
| 408 | }, | |
| 409 | .index => { | |
| 410 | return s.addInstRef(.array_len, .{ | |
| 411 | .operand = base, | |
| 412 | }); | |
| 413 | }, | |
| 414 | } | |
| 415 | } | |
| 416 | ||
| 417 | // Arrays use runtime known int32s | |
| 418 | const rhs = try s.coerceRef(in.op(.rhs), rhs_raw, .int32); | |
| 419 | return s.addInstRef(.index, .{ | |
| 420 | .lhs = base, | |
| 421 | .rhs = rhs, | |
| 422 | }); | |
| 423 | } | |
| 424 | ||
| 425 | const rhs = try s.resolveCoerceValue(in.op(.rhs), field.rhs, .string); | |
| 426 | const string = rhs.asString(s.ip); | |
| 427 | ||
| 428 | if (ty == .@"opaque") { | |
| 429 | return s.addInstRef(.field_opaque, .{ | |
| 430 | .base = base, | |
| 431 | .field = string.tryToNullTerminatedString(s.ip, s.gpa) catch |e| switch (e) { | |
| 432 | error.HasNulls => @panic("TODO: handle null"), | |
| 433 | else => return e, | |
| 434 | }, | |
| 435 | }); | |
| 436 | } | |
| 437 | ||
| 438 | if (ty.toStructType(s.ip)) |struct_type| { | |
| 439 | const found_index: u32 = found_index: { | |
| 440 | const lookup = string.tryToNullTerminatedString( | |
| 441 | s.ip, | |
| 442 | s.gpa, | |
| 443 | ) catch |e| switch (e) { | |
| 444 | error.HasNulls => break :found_index null, | |
| 445 | else => return e, | |
| 446 | }; | |
| 447 | ||
| 448 | break :found_index for (struct_type.field_keys, 0..) |key, i| { | |
| 449 | if (key == lookup) | |
| 450 | break @as(u32, @intCast(i)); | |
| 451 | } else null; | |
| 452 | } orelse { | |
| 453 | return s.fail(.field_lookup_failed, .{ | |
| 454 | .ty = ty, | |
| 455 | .src = in.op(.rhs).lazyNode(s.decl_index), | |
| 456 | .field = rhs, | |
| 457 | }); | |
| 458 | }; | |
| 459 | ||
| 460 | switch (base.decode()) { | |
| 461 | .value => |value| { | |
| 462 | _, const trail = value.valueData(s.ip, .struct_value).getTrail(s.ip); | |
| 463 | return trail.at(&s.ip.extra, Value, found_index).toAir(); | |
| 464 | }, | |
| 465 | .index => |index| { | |
| 466 | return s.addInstRef(.field_struct, .{ | |
| 467 | .base = index, | |
| 468 | .field_index = found_index, | |
| 469 | }); | |
| 470 | }, | |
| 471 | } | |
| 472 | } | |
| 473 | ||
| 474 | return s.fail(.field_invalid_type, .{ | |
| 475 | .ty = ty, | |
| 476 | .src = in.op(.rhs).lazyNode(s.decl_index), | |
| 477 | }); | |
| 478 | }, | |
| 479 | .load_decl => { | |
| 480 | const request: Teg.StrNode = s.code.instData(.load_decl, in); | |
| 481 | const decl_name = try s.ip.getOrPutString( | |
| 482 | s.gpa, | |
| 483 | request.string.slice(s.code.string_bytes), | |
| 484 | .no_embedded_nulls, | |
| 485 | ); | |
| 486 | const decl_index = s.lookupDecl(decl_name) orelse { | |
| 487 | std.debug.print("did not find {s}\n", .{decl_name.toSlice(s.ip)}); | |
| 488 | return error.AnalysisFail; | |
| 489 | }; | |
| 490 | const resolved = try s.resolveDecl(in.op(.main_node), decl_index); | |
| 491 | return resolved.toAir(); | |
| 492 | }, | |
| 493 | .param => { | |
| 494 | const param: Teg.DataNode = s.code.instData(.param, in); | |
| 495 | return s.addInstRef(.arg, .{ | |
| 496 | .type = s.params[param.data], | |
| 497 | .arg_number = param.data, | |
| 498 | }); | |
| 499 | }, | |
| 500 | inline .add, | |
| 524 | .add, | |
| 501 | 525 | .sub, |
| 502 | 526 | .mul, |
| 503 | 527 | .div, |
| 504 | 528 | .lt, |
| 505 | 529 | .gt, |
| 506 | 530 | .eq, |
| 507 | => |tag| { | |
| 508 | const bin: Teg.Bin = s.code.instPayload(tag, in); | |
| 531 | => { | |
| 532 | const bin: Teg.Bin = s.frame.currentPayload(tag); | |
| 509 | 533 | const op = @field(BinaryOp, @tagName(tag)); |
| 510 | 534 | |
| 511 | const lhs = try s.resolveRef(bin.lhs); | |
| 512 | const rhs = try s.resolveRef(bin.rhs); | |
| 513 | ||
| 535 | const lhs = s.getRef(bin.lhs, .marked); | |
| 514 | 536 | const left_ty = s.typeOfRef(lhs); |
| 537 | ||
| 538 | const rhs = s.getRef(bin.rhs, .marked); | |
| 515 | 539 | const right_ty = s.typeOfRef(rhs); |
| 516 | 540 | |
| 517 | const ty = try s.binTypeResolution(in, op, left_ty, right_ty); | |
| 541 | const ty = try s.binTypeResolution( | |
| 542 | s.frame.currentIndex(), | |
| 543 | op, | |
| 544 | left_ty, | |
| 545 | right_ty, | |
| 546 | ); | |
| 518 | 547 | |
| 519 | const left = try s.coerceRef(in.op(.lhs), lhs, ty); | |
| 520 | const right = try s.coerceRef(in.op(.rhs), rhs, ty); | |
| 548 | const left = try s.coerceRef(s.lazyNode(.lhs), lhs, ty); | |
| 549 | const right = try s.coerceRef(s.lazyNode(.rhs), rhs, ty); | |
| 521 | 550 | |
| 522 | 551 | if (left.kind == .value and right.kind == .value) { |
| 523 | 552 | return (try s.comptimeBin( |
| ... | ... | @@ -527,37 +556,23 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 527 | 556 | )).toAir(); |
| 528 | 557 | } |
| 529 | 558 | |
| 530 | return s.addInstRef(tag.airTag(), .{ | |
| 559 | try s.ensureAtRuntime(s.lazyNode(.main_node)); | |
| 560 | ||
| 561 | return s.addRef(tag.airTag(), .{ | |
| 531 | 562 | .lhs = left, |
| 532 | 563 | .rhs = right, |
| 533 | 564 | }); |
| 534 | 565 | }, |
| 535 | .bool_not => { | |
| 536 | const un: Teg.RefNode = s.code.instData(.bool_not, in); | |
| 537 | ||
| 538 | const operand = try s.resolveCoerceRef(in.op(.operand), un.operand, .bool); | |
| 539 | ||
| 540 | // Constant propagation | |
| 541 | if (operand.value()) |value| { | |
| 542 | assert(value == .bool_false or value == .bool_true); | |
| 543 | return if (value == .bool_true) | |
| 544 | Value.bool_false.toAir() | |
| 545 | else | |
| 546 | Value.bool_true.toAir(); | |
| 547 | } | |
| 548 | ||
| 549 | return s.addInstRef(.bool_not, .{ .operand = operand }); | |
| 550 | }, | |
| 551 | 566 | .negate => { |
| 552 | const un: Teg.RefNode = s.code.instData(.negate, in); | |
| 567 | const un: Teg.RefNode = s.frame.currentData(.negate); | |
| 553 | 568 | |
| 554 | const operand = try s.resolveRef(un.operand); | |
| 569 | const operand = s.getRef(un.operand, .marked); | |
| 555 | 570 | |
| 556 | 571 | const ty = s.typeOfRef(operand); |
| 557 | 572 | switch (ty) { |
| 558 | 573 | .numeric_literal, .int32, .float => {}, |
| 559 | 574 | else => return s.fail(.un_op_mismatch, .{ |
| 560 | .src = in.op(.operand).lazyNode(s.decl_index), | |
| 575 | .src = s.lazyNode(.operand), | |
| 561 | 576 | .ty = ty, |
| 562 | 577 | }), |
| 563 | 578 | } |
| ... | ... | @@ -568,34 +583,128 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 568 | 583 | }); |
| 569 | 584 | } |
| 570 | 585 | |
| 571 | return s.addInstRef(.negate, .{ .operand = operand }); | |
| 586 | return s.addRef(.negate, .{ .operand = operand }); | |
| 572 | 587 | }, |
| 573 | .@"break" => { | |
| 574 | const brk: Teg.Break = s.code.instPayload(.@"break", in); | |
| 575 | const operand = try s.resolveRef(brk.operand); | |
| 576 | const block_ref = s.inst_map.get(brk.block).?.decode().index; // block ref is resolved by teg | |
| 588 | .bool_not => { | |
| 589 | const un: Teg.RefNode = s.frame.currentData(.bool_not); | |
| 577 | 590 | |
| 578 | const coerced = if (s.temp_result_map.get(brk.block)) |ty| | |
| 579 | try s.coerceRef(in.op(.operand), operand, ty) | |
| 580 | else | |
| 581 | operand; | |
| 591 | const operand = try s.getCoercedRef(s.lazyNode(.operand), un.operand, .bool, .marked); | |
| 592 | ||
| 593 | if (operand.value()) |value| { | |
| 594 | return switch (value) { | |
| 595 | .bool_false => Value.bool_true.toAir(), | |
| 596 | .bool_true => Value.bool_false.toAir(), | |
| 597 | else => unreachable, | |
| 598 | }; | |
| 599 | } | |
| 600 | ||
| 601 | try s.ensureAtRuntime(s.lazyNode(.operand)); | |
| 602 | ||
| 603 | return s.addRef(.bool_not, .{ .operand = operand }); | |
| 604 | }, | |
| 605 | .struct_decl => { | |
| 606 | const node: Teg.StructDecl = s.frame.currentPayload(.struct_decl); | |
| 582 | 607 | |
| 583 | return s.addInstRef(.@"break", .{ | |
| 584 | .block_ref = block_ref, | |
| 585 | .value = coerced, | |
| 608 | const scratch = try s.scratch.reserveMulti( | |
| 609 | s.arena(), | |
| 610 | .{ .keys = NullTerminatedString, .types = Type }, | |
| 611 | .{ node.field_len, node.field_len }, | |
| 612 | ); | |
| 613 | defer scratch.deinit(); | |
| 614 | ||
| 615 | // Fill the first half with keys | |
| 616 | for (scratch.items(.keys), node.keys(&s.teg)) |*dest, src| { | |
| 617 | dest.* = try s.ip.getOrPutString(s.gpa, src.slice(s.teg.string_bytes), .no_embedded_nulls); | |
| 618 | } | |
| 619 | ||
| 620 | // Fill the second half with coerced values. | |
| 621 | for (node.types(&s.teg), 0..) |ty, i| { | |
| 622 | scratch.at(.types, i).* = try s.getType(s.lazyNode(.argument(@intCast(i))), ty); | |
| 623 | } | |
| 624 | ||
| 625 | return try s.makeValueAir(.{ .struct_type = .{ | |
| 626 | .field_keys = scratch.items(.keys), | |
| 627 | .field_types = scratch.items(.types), | |
| 628 | } }); | |
| 629 | }, | |
| 630 | .discard => { | |
| 631 | const operand = s.getRef(s.frame.currentData(.discard).operand, .no_mark); | |
| 632 | const t = s.typeOfRef(operand); | |
| 633 | if (t == .void) { | |
| 634 | std.debug.print("oh no, we have a useless _ = void;\n", .{}); | |
| 635 | } | |
| 636 | }, | |
| 637 | // TODO: these are disabled since calling opaque functions will return an opaque, | |
| 638 | // which should be discarded, but these are going to be muted for now until some | |
| 639 | // common apis are available in the standard library | |
| 640 | .ensure_void => { | |
| 641 | const operand = s.getRef(s.frame.currentData(.ensure_void).operand, .no_mark); | |
| 642 | _ = s.typeOfRef(operand); | |
| 643 | }, | |
| 644 | .@"extern" => { | |
| 645 | const operand_ref = s.frame.currentData(.@"extern").operand; | |
| 646 | const operand = try s.getCoercedValue(s.lazyNode(.operand), operand_ref, .string); | |
| 647 | const str = operand.asString(s.ip).tryToNullTerminatedString(s.ip, s.gpa) catch { | |
| 648 | @panic("TODO: support strings with null here"); | |
| 649 | }; | |
| 650 | return try s.makeValueAir(.{ .external_value = .{ | |
| 651 | .key = &.{str}, | |
| 652 | } }); | |
| 653 | }, | |
| 654 | .field => { | |
| 655 | const field: Teg.Bin = s.frame.currentPayload(.field); | |
| 656 | const lhs = s.getRef(field.lhs, .marked); | |
| 657 | const lhs_type = s.typeOfRef(lhs); | |
| 658 | ||
| 659 | if (lhs_type == .@"opaque") { | |
| 660 | try s.ensureAtRuntime(s.lazyNode(.lhs)); | |
| 661 | return s.addRef(.field_opaque, .{ | |
| 662 | .lhs = lhs, | |
| 663 | .rhs = s.getRef(field.rhs, .marked), | |
| 664 | }); | |
| 665 | } else if (lhs_type.toArrayType(s.ip) != null) { | |
| 666 | return try s.fieldAccessArray(lhs, lhs_type, field.rhs); | |
| 667 | } else if (lhs_type.toStructType(s.ip)) |struct_type| { | |
| 668 | return try s.fieldAccessStruct(lhs, lhs_type, struct_type, field.rhs); | |
| 669 | } else if (lhs.value()) |value| { | |
| 670 | if (value.valueTag(s.ip) == .external_value) { | |
| 671 | const rhs = try s.getCoercedValue(s.lazyNode(.rhs), field.rhs, .string); | |
| 672 | const field_key = rhs.asString(s.ip).tryToNullTerminatedString(s.ip, s.gpa) catch { | |
| 673 | @panic("TODO: support null in these strings"); | |
| 674 | }; | |
| 675 | ||
| 676 | const previous_key = value.toKey(s.ip).external_value.key; | |
| 677 | ||
| 678 | const new_key = try std.mem.concat(s.arena(), NullTerminatedString, &.{ | |
| 679 | previous_key, | |
| 680 | &.{field_key}, | |
| 681 | }); | |
| 682 | defer s.arena().free(new_key); | |
| 683 | ||
| 684 | return s.makeValueAir(.{ .external_value = .{ | |
| 685 | .key = new_key, | |
| 686 | } }); | |
| 687 | } | |
| 688 | } | |
| 689 | ||
| 690 | return s.fail(.field_invalid_type, .{ | |
| 691 | .ty = lhs_type, | |
| 692 | .src = s.lazyNode(.rhs), | |
| 586 | 693 | }); |
| 587 | 694 | }, |
| 588 | 695 | .call => { |
| 589 | const call: Teg.Call = s.code.instPayload(.call, in); | |
| 590 | const base = try s.resolveRef(call.base); | |
| 696 | const call: Teg.Call = s.frame.currentPayload(.call); | |
| 697 | const base = s.getRef(call.base, .marked); | |
| 591 | 698 | const base_type = s.typeOfRef(base); |
| 592 | 699 | |
| 593 | const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len); | |
| 594 | defer s.arena.free(scratch); | |
| 700 | try s.ensureAtRuntime(s.lazyNode(.main_node)); // todo: comptime calls | |
| 701 | ||
| 702 | const scratch = try s.arena().alloc(Air.Inst.Ref, call.args.len); | |
| 703 | defer s.arena().free(scratch); | |
| 595 | 704 | if (base_type != .@"opaque") { |
| 596 | 705 | const proto = base_type.toFunctionPrototype(s.ip) orelse { |
| 597 | 706 | return s.fail(.call_non_function, .{ |
| 598 | .call = s.nodeLoc(in, .operand), | |
| 707 | .call = s.lazyNode(.operand), | |
| 599 | 708 | .ty = base_type, |
| 600 | 709 | }); |
| 601 | 710 | }; |
| ... | ... | @@ -603,38 +712,38 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 603 | 712 | if (proto.param_types.len != call.args.len) { |
| 604 | 713 | // TODO: also attempt to coerce some of the params to reveal more errors. |
| 605 | 714 | return s.fail(.call_mismatch_args, .{ |
| 606 | .call = s.nodeLoc(in, .operand), | |
| 715 | .call = s.lazyNode(.operand), | |
| 607 | 716 | .actual = @intCast(call.args.len), |
| 608 | 717 | .expected = @intCast(proto.param_types.len), |
| 609 | 718 | }); |
| 610 | 719 | } |
| 611 | 720 | |
| 612 | 721 | // validate function args |
| 613 | for (proto.param_types, call.args.slice(&s.code), 0..) |param, arg, i| { | |
| 614 | const ref = try s.resolveRef(arg); | |
| 722 | for (proto.param_types, call.args.slice(&s.teg), 0..) |param, arg, i| { | |
| 723 | const ref = s.getRef(arg, .marked); | |
| 615 | 724 | const coerced = try s.coerceRef( |
| 616 | in.op(operandArgument(@intCast(i))), | |
| 725 | s.lazyNode(.argument(@intCast(i))), | |
| 617 | 726 | ref, |
| 618 | 727 | param, |
| 619 | 728 | ); |
| 620 | 729 | scratch[i] = coerced; |
| 621 | 730 | } |
| 622 | 731 | } else { |
| 623 | for (call.args.slice(&s.code), 0..) |arg, i| { | |
| 732 | for (call.args.slice(&s.teg), 0..) |arg, i| { | |
| 624 | 733 | const ref = try s.ensureValueDefinedRepresentation( |
| 625 | in.op(operandArgument(@intCast(i))), | |
| 626 | try s.resolveRef(arg), | |
| 734 | s.lazyNode(.argument(@intCast(i))), | |
| 735 | s.getRef(arg, .marked), | |
| 627 | 736 | ); |
| 628 | 737 | scratch[i] = ref; |
| 629 | 738 | } |
| 630 | 739 | } |
| 631 | 740 | |
| 632 | const start = s.air_extra.list.items.len; | |
| 633 | try s.air_extra.list.appendSlice(s.gpa, @ptrCast(scratch)); | |
| 741 | const start = s.func.?.extra.list.items.len; | |
| 742 | try s.func.?.extra.list.appendSlice(s.gpa, @ptrCast(scratch)); | |
| 634 | 743 | |
| 635 | return s.addInstRef(.call, .{ | |
| 744 | return s.addRef(.call, .{ | |
| 636 | 745 | .base = base, |
| 637 | .call_data = try s.air_extra.append(s.gpa, Air.Call.Data, .{ | |
| 746 | .call_data = try s.func.?.extra.append(s.gpa, Air.Call.Data, .{ | |
| 638 | 747 | .args = .{ |
| 639 | 748 | .start = @intCast(start), |
| 640 | 749 | .len = @intCast(scratch.len), |
| ... | ... | @@ -642,178 +751,389 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 642 | 751 | }), |
| 643 | 752 | }); |
| 644 | 753 | }, |
| 754 | .array_init => { | |
| 755 | const node: Teg.ArrayInit = s.frame.currentPayload(.array_init); | |
| 756 | ||
| 757 | assert(node.list.len > 0 or node.type_hint.unwrap() != null); | |
| 758 | ||
| 759 | const scratch = try s.scratch.reserve(s.arena(), Air.Inst.Ref, node.list.len); | |
| 760 | defer scratch.deinit(); | |
| 761 | ||
| 762 | var item_type: ?Type = if (node.type_hint.unwrap()) |ty| | |
| 763 | try s.getType(s.lazyNode(.operand), ty) | |
| 764 | else | |
| 765 | null; | |
| 766 | ||
| 767 | var is_constant = true; | |
| 768 | ||
| 769 | for (node.list.slice(&s.teg), 0..) |item_teg_ref, i| { | |
| 770 | const item = s.getRef(item_teg_ref, .marked); | |
| 771 | const coerced = if (item_type) |ty| | |
| 772 | try s.coerceRef(s.lazyNode(.argument(@intCast(i))), item, ty) | |
| 773 | else brk: { | |
| 774 | item_type = s.typeOfRef(item); | |
| 775 | break :brk item; | |
| 776 | }; | |
| 777 | ||
| 778 | if (coerced.kind == .index) is_constant = false; | |
| 779 | ||
| 780 | scratch.at(i).* = coerced; | |
| 781 | } | |
| 782 | ||
| 783 | const ty = try s.makeType(.{ .array_type = .{ | |
| 784 | .item_type = item_type orelse unreachable, | |
| 785 | } }); | |
| 786 | ||
| 787 | if (is_constant) { | |
| 788 | // Convert all items in storage into a Value type | |
| 789 | const result_storage = scratch.sliceReinterpret(Value); | |
| 790 | for (scratch.slice(), scratch.sliceReinterpret(Value)) |ref, *value| { | |
| 791 | value.* = ref.decode().value; | |
| 792 | } | |
| 793 | return try s.makeValueAir(.{ .array_value = .{ | |
| 794 | .type = ty, | |
| 795 | .items = result_storage, | |
| 796 | } }); | |
| 797 | } else { | |
| 798 | // Create a runtime array instantiation | |
| 799 | try s.ensureAtRuntime(s.lazyNode(.main_node)); | |
| 800 | return try s.addRef(.array_init, .{ | |
| 801 | .ty = ty, | |
| 802 | .list = try s.func.?.extra.append(s.gpa, Air.RefList, .{ | |
| 803 | .start = try s.func.?.extra.appendSlice(s.gpa, scratch.slice()), | |
| 804 | .len = node.list.len, | |
| 805 | }), | |
| 806 | }); | |
| 807 | } | |
| 808 | }, | |
| 809 | ||
| 810 | .memory => { | |
| 811 | const data = s.frame.currentData(.memory); | |
| 812 | ||
| 813 | try s.ensureAtRuntime(s.lazyNode(.main_node)); | |
| 814 | ||
| 815 | const initial_value = s.getRef(data.operand, .marked); | |
| 816 | ||
| 817 | const runtime_known = try s.ensureValueRuntime(s.lazyNode(.operand), initial_value); | |
| 818 | ||
| 819 | return try s.addRef(.memory, .{ | |
| 820 | .init = runtime_known, | |
| 821 | .ty = s.typeOfRef(initial_value), | |
| 822 | }); | |
| 823 | }, | |
| 824 | .store => { | |
| 825 | const data: Teg.Store = s.frame.currentPayload(.store); | |
| 826 | assert(s.teg.instTag(data.memory) == .memory); // corrupt store instruction | |
| 827 | ||
| 828 | try s.ensureAtRuntime(s.lazyNode(.main_node)); // TODO | |
| 829 | ||
| 830 | const location = s.inst_map.get(data.memory).index() orelse | |
| 831 | unreachable; // memory must map to a Air ref | |
| 832 | s.markInst(location); | |
| 833 | ||
| 834 | const operand = try s.getCoercedRef( | |
| 835 | s.lazyNode(.rhs), | |
| 836 | data.operand, | |
| 837 | s.typeOfRef(location.toRef()), | |
| 838 | .marked, | |
| 839 | ); | |
| 840 | ||
| 841 | return try s.addRef(.store, .{ | |
| 842 | .memory = location, | |
| 843 | .operand = operand, | |
| 844 | }); | |
| 845 | }, | |
| 846 | ||
| 645 | 847 | .js_new => { |
| 646 | const call: Teg.JsNew = s.code.instPayload(.js_new, in); | |
| 647 | const base = try s.resolveRef(call.base); | |
| 848 | const call: Teg.JsNew = s.frame.currentPayload(.js_new); | |
| 849 | ||
| 850 | try s.ensureAtRuntime(s.lazyNode(.main_node)); | |
| 851 | ||
| 852 | const base = s.getRef(call.base, .marked); | |
| 648 | 853 | |
| 649 | const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len); | |
| 650 | defer s.arena.free(scratch); | |
| 651 | for (call.args.slice(&s.code), 0..) |arg, i| { | |
| 854 | const scratch = try s.arena().alloc(Air.Inst.Ref, call.args.len); | |
| 855 | defer s.arena().free(scratch); | |
| 856 | for (call.args.slice(&s.teg), 0..) |arg, i| { | |
| 652 | 857 | const ref = try s.ensureValueDefinedRepresentation( |
| 653 | in.op(operandArgument(@intCast(i))), | |
| 654 | try s.resolveRef(arg), | |
| 858 | s.lazyNode(.argument(@intCast(i))), | |
| 859 | s.getRef(arg, .marked), | |
| 655 | 860 | ); |
| 656 | 861 | scratch[i] = ref; |
| 657 | 862 | } |
| 658 | 863 | |
| 659 | const start = s.air_extra.list.items.len; | |
| 660 | try s.air_extra.list.appendSlice(s.gpa, @ptrCast(scratch)); | |
| 864 | const start = s.func.?.extra.list.items.len; | |
| 865 | try s.func.?.extra.list.appendSlice(s.gpa, @ptrCast(scratch)); | |
| 661 | 866 | |
| 662 | return s.addInstRef(.js_new, .{ | |
| 867 | return s.addRef(.js_new, .{ | |
| 663 | 868 | .base = base, |
| 664 | .call_data = try s.air_extra.append(s.gpa, Air.Call.Data, .{ .args = .{ | |
| 869 | .call_data = try s.func.?.extra.append(s.gpa, Air.Call.Data, .{ .args = .{ | |
| 665 | 870 | .start = @intCast(start), |
| 666 | 871 | .len = @intCast(scratch.len), |
| 667 | 872 | } }), |
| 668 | 873 | }); |
| 669 | 874 | }, |
| 670 | .ensure_void => { | |
| 671 | const operand = try s.resolveRefExtra(s.code.instData(.ensure_void, in).operand, false); | |
| 672 | const t = s.typeOfRef(operand); | |
| 673 | _ = t; | |
| 674 | // if (!t.isEnsureVoid(s.ip)) { | |
| 675 | // std.debug.print("oh no, we are discarding {} on accident\n", .{ | |
| 676 | // t.fmt(s.ip), | |
| 677 | // }); | |
| 678 | // } | |
| 679 | return InternPool.Value.void.toAir(); | |
| 875 | ||
| 876 | // .assert => { | |
| 877 | // return Value.void.toAir(); | |
| 878 | // }, | |
| 879 | ||
| 880 | // control flow | |
| 881 | .block => unreachable, // frame should already be pushed | |
| 882 | .@"comptime" => { | |
| 883 | const index = s.frame.currentIndex(); | |
| 884 | try s.pushFrame(); | |
| 885 | s.frame = Frame.initFromBlock(s, index, .compile_time, .none); | |
| 680 | 886 | }, |
| 681 | .discard => { | |
| 682 | const operand = try s.resolveRefExtra(s.code.instData(.discard, in).operand, false); | |
| 683 | const t = s.typeOfRef(operand); | |
| 684 | if (t == .void) { | |
| 685 | std.debug.print("oh no, we have a useless _ = void;\n", .{}); | |
| 887 | ||
| 888 | .func => { | |
| 889 | const data: Teg.Func = s.frame.currentPayload(.func); | |
| 890 | const reserve = try s.scratch.reserveMulti( | |
| 891 | s.arena(), | |
| 892 | .{ .params = Type, .captures = Air.Inst.Ref }, | |
| 893 | .{ data.params.len, data.captures.len }, | |
| 894 | ); | |
| 895 | defer reserve.deinit(); | |
| 896 | if (data.captures.len > 0) @panic("captures"); | |
| 897 | ||
| 898 | for (data.params.slice(&s.teg), 0..) |param, i| { | |
| 899 | const src = s.lazyNode(.argument(@intCast(i))); | |
| 900 | const ty = try s.getType(src, param.unwrap() orelse @panic("TODO: inferred param sets")); | |
| 901 | if (!ty.hasPossibleRepresentation(s.ip)) { | |
| 902 | return s.fail(.param_comptime_only, .{ | |
| 903 | .src = src, | |
| 904 | .type = ty, | |
| 905 | }); | |
| 906 | } | |
| 907 | reserve.items(.params)[i] = ty; | |
| 686 | 908 | } |
| 687 | return InternPool.Value.void.toAir(); | |
| 909 | ||
| 910 | const return_type = try s.getType( | |
| 911 | s.lazyNode(.return_type), | |
| 912 | data.return_type.unwrap() orelse .void, | |
| 913 | ); | |
| 914 | ||
| 915 | const tracked_inst = try s.ip.trackInst(s.gpa, s.file, s.frame.currentIndex()); | |
| 916 | const func_type = try s.makeType(.{ | |
| 917 | .function_type = .{ | |
| 918 | .param_types = reserve.items(.params), | |
| 919 | .return_type = return_type, | |
| 920 | }, | |
| 921 | }); | |
| 922 | const func_value = try s.makeValue(.{ | |
| 923 | .func = .{ | |
| 924 | .ty = func_type, | |
| 925 | .owner_decl = s.techDebtDeclIndex(), // TODO: ooh, this is a fun one! | |
| 926 | .teg_body_inst = tracked_inst, | |
| 927 | }, | |
| 928 | }); | |
| 929 | ||
| 930 | s.inst_map.add(s.frame.currentIndex(), func_value.toAir()); | |
| 931 | ||
| 932 | s.frame.advance(); | |
| 933 | ||
| 934 | // TODO: move this queue once we perform one of these | |
| 935 | // - depend on the return type of an inferred-return-type function | |
| 936 | // - use the value in any runtime context () | |
| 937 | try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } }); | |
| 688 | 938 | }, |
| 689 | .@"if" => { | |
| 690 | const data: Teg.If = s.code.instData(.@"if", in).extra_index.get(&s.code); | |
| 939 | .@"break" => { | |
| 940 | const brk: Teg.Break = s.frame.currentPayload(.@"break"); | |
| 941 | const operand = s.getRef(brk.operand, .marked); | |
| 942 | ||
| 943 | assert(s.frame.block_index == brk.block); | |
| 944 | ||
| 945 | const mark = core.U32Stack.Mark{ .stack = &s.scratch, .index = s.frame.mark_index }; | |
| 946 | ||
| 947 | if (s.frame_stack.items.len == 0) { | |
| 948 | s.inst_map.add(s.frame.block_index, operand); | |
| 949 | ||
| 950 | if (s.func) |*func| { | |
| 951 | const indexes = mark.slice(u32); | |
| 952 | try func.extra.list.ensureUnusedCapacity(s.gpa, indexes.len + 1); | |
| 953 | const start = func.extra.list.items.len; | |
| 954 | func.extra.list.appendSliceAssumeCapacity(indexes); | |
| 955 | func.extra.list.appendAssumeCapacity(@intFromEnum(try s.addIndexUntracked(.@"break", .{ | |
| 956 | .block = .main_block, | |
| 957 | .value = operand, | |
| 958 | }))); | |
| 959 | func.instructions.items(.data)[0] = .encode(.block, .{ | |
| 960 | .start = @intCast(start), | |
| 961 | .len = @intCast(indexes.len + 1), | |
| 962 | }); | |
| 963 | } | |
| 964 | ||
| 965 | return .stop; | |
| 966 | } | |
| 967 | ||
| 968 | const data = s.frame.data; | |
| 969 | s.popFrame(); | |
| 970 | ||
| 971 | switch (s.frame.currentTag()) { | |
| 972 | .@"comptime" => { | |
| 973 | s.inst_map.add(s.frame.currentIndex(), operand); | |
| 974 | s.frame.advance(); | |
| 975 | }, | |
| 976 | .@"if" => { | |
| 977 | const if_data: Teg.If = s.frame.currentPayload(.@"if"); | |
| 978 | ||
| 979 | switch (data.conditional) { | |
| 980 | .comptime_condition => { | |
| 981 | s.inst_map.add(s.frame.currentIndex(), operand); | |
| 982 | s.frame.advance(); | |
| 983 | return .@"continue"; | |
| 984 | }, | |
| 985 | .runtime_yes => { | |
| 986 | try s.emitRuntimeBlock(if_data.yes.decode().index, operand, mark); | |
| 987 | ||
| 988 | switch (if_data.no.decode()) { | |
| 989 | .value => { | |
| 990 | // There is no instruction to jump to | |
| 991 | }, | |
| 992 | .index => |block| { | |
| 993 | const mode = s.frame.mode; | |
| 994 | try s.pushFrame(); | |
| 995 | s.frame = Frame.initFromBlock(s, block, mode, .{ | |
| 996 | .conditional = .runtime_no, | |
| 997 | }); | |
| 998 | return .@"continue"; | |
| 999 | }, | |
| 1000 | } | |
| 1001 | }, | |
| 1002 | .runtime_no => { | |
| 1003 | try s.emitRuntimeBlock(if_data.no.decode().index, operand, mark); | |
| 1004 | mark.deinit(); | |
| 1005 | }, | |
| 1006 | } | |
| 691 | 1007 | |
| 692 | const cond_raw = try s.resolveRef(data.cond); | |
| 693 | const cond = try s.coerceRef(in.op(.operand), cond_raw, .bool); | |
| 1008 | assert(s.frame.mode == .run_time); | |
| 694 | 1009 | |
| 695 | // If known at comptime, only analyze the taken path | |
| 696 | if (cond.kind == .value) { | |
| 697 | @panic("TODO: comptime IF"); | |
| 698 | } | |
| 1010 | const yes = s.getRef(if_data.yes, .no_mark); | |
| 1011 | const no = s.getRef(if_data.no, .no_mark); | |
| 699 | 1012 | |
| 700 | // Emit a runtime branch | |
| 701 | const yes_block = try s.analyzeRefToBlock(data.yes); | |
| 702 | const no_block = try s.analyzeRefToBlock(data.no); | |
| 1013 | const union_type = s.unionType(s.typeOfRef(yes), s.typeOfRef(no)); | |
| 703 | 1014 | |
| 704 | return try s.addInstRef(.@"if", .{ | |
| 705 | .cond = cond, | |
| 706 | .data = try s.air_extra.append(s.gpa, Air.If.Data, .{ | |
| 707 | .yes = yes_block, | |
| 708 | .no = no_block, | |
| 709 | .union_ty = s.unionType(s.typeOfRef(yes_block), s.typeOfRef(no_block)), | |
| 710 | }), | |
| 711 | }); | |
| 712 | }, | |
| 713 | .block => { | |
| 714 | const block = try s.analyzeBlock(in); | |
| 715 | switch (block.decode()) { | |
| 716 | .index => |t| if (s.tempAir().instTag(t) == .block) { | |
| 717 | try s.scratch.add(s.gpa, Air.Inst.Index, t); | |
| 1015 | // const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yes, union_type); | |
| 1016 | // const no_coerced = try s.coerceValue(s.lazyNode(.rhs), no, union_type); | |
| 1017 | ||
| 1018 | s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{ | |
| 1019 | .cond = s.getRef(if_data.cond, .no_mark), | |
| 1020 | .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{ | |
| 1021 | .yes = yes, | |
| 1022 | .no = no, | |
| 1023 | .union_ty = union_type, | |
| 1024 | }), | |
| 1025 | })); | |
| 1026 | s.frame.advance(); | |
| 718 | 1027 | }, |
| 719 | else => {}, | |
| 1028 | else => |t| std.debug.panic("TODO: post-visit .{s}", .{@tagName(t)}), | |
| 720 | 1029 | } |
| 721 | return block; | |
| 722 | }, | |
| 723 | .memory => { | |
| 724 | const data = s.code.instData(.memory, in); | |
| 725 | ||
| 726 | const initial_value = try s.resolveRef(data.operand); | |
| 727 | 1030 | |
| 728 | const runtime_known = try s.ensureValueRuntime( | |
| 729 | in.op(.operand), | |
| 730 | initial_value, | |
| 731 | ); | |
| 732 | ||
| 733 | return try s.addInstRef(.memory, .{ | |
| 734 | .init = runtime_known, | |
| 735 | .ty = s.typeOfRef(initial_value), | |
| 736 | }); | |
| 1031 | return .@"continue"; | |
| 737 | 1032 | }, |
| 738 | .store => { | |
| 739 | const data: Teg.Store = s.code.instPayload(.store, in); | |
| 740 | assert(s.code.instTag(data.memory) == .memory); // corrupt store instruction | |
| 741 | 1033 | |
| 742 | const location = s.mapTeg(data.memory.toRef()).decode().index; | |
| 743 | s.markInst(location); | |
| 744 | const operand = try s.resolveRef(data.operand); | |
| 745 | const operand_coerced = try s.coerceRef( | |
| 746 | in.op(.rhs), | |
| 747 | operand, | |
| 748 | s.typeOfRef(location.toRef()), | |
| 1034 | .load_decl => { | |
| 1035 | const request: Teg.StrNode = s.frame.currentData(.load_decl); | |
| 1036 | const decl_name = try s.ip.getOrPutString( | |
| 1037 | s.gpa, | |
| 1038 | request.string.slice(s.teg.string_bytes), | |
| 1039 | .no_embedded_nulls, | |
| 749 | 1040 | ); |
| 750 | return try s.addInstRef(.store, .{ | |
| 751 | .memory = location, | |
| 752 | .operand = operand_coerced, | |
| 753 | }); | |
| 1041 | const decl_index = s.lookupDecl(decl_name); | |
| 1042 | return (try s.getDecl(decl_index)).toAir(); | |
| 754 | 1043 | }, |
| 1044 | ||
| 755 | 1045 | .coercion => { |
| 756 | const as: Teg.Coercion = s.code.instPayload(.coercion, in); | |
| 757 | const coerce_to = try s.resolveCoerceValue(in.op(.lhs), as.type, .type); | |
| 758 | const operand = try s.resolveRef(as.operand); | |
| 759 | return try s.coerceRef(in.op(.rhs), operand, coerce_to.toType(s.ip).?); | |
| 760 | }, | |
| 761 | .@"comptime" => { | |
| 762 | const node: Teg.RefNode = s.code.instData(.@"comptime", in); | |
| 763 | const value = try s.resolveValue(node.operand); | |
| 764 | return value.toAir(); | |
| 765 | }, | |
| 766 | .type_of => { | |
| 767 | const node: Teg.RefNode = s.code.instData(.type_of, in); | |
| 768 | const value = try s.resolveValue(node.operand); | |
| 769 | return value.typeOf(s.ip).value().toAir(); | |
| 1046 | const as: Teg.Coercion = s.frame.currentPayload(.coercion); | |
| 1047 | const coerce_to = try s.getType(s.lazyNode(.operand), as.type); | |
| 1048 | return try s.getCoercedRef(s.lazyNode(.operand), as.operand, coerce_to, .marked); | |
| 770 | 1049 | }, |
| 1050 | ||
| 771 | 1051 | .compile_error => { |
| 772 | const node: Teg.RefNode = s.code.instData(.compile_error, in); | |
| 773 | const value = try s.resolveCoerceValue( | |
| 774 | in.op(operandArgument(0)), | |
| 775 | node.operand, | |
| 776 | .string, | |
| 777 | ); | |
| 1052 | const node: Teg.RefNode = s.frame.currentData(.compile_error); | |
| 1053 | const value = try s.getCoercedValue(s.lazyNode(.operand), node.operand, .string); | |
| 778 | 1054 | const str = value.asString(s.ip).toSlice(s.ip); |
| 779 | 1055 | return s.fail(.user_compile_error, .{ |
| 780 | .src = s.lazyNode(in.op(.main_node)), | |
| 1056 | .src = s.lazyNode(.main_node), | |
| 781 | 1057 | .msg = try s.report.addString(str), |
| 782 | 1058 | }); |
| 783 | 1059 | }, |
| 784 | .struct_decl => { | |
| 785 | const node: Teg.StructDecl = s.code.instPayload(.struct_decl, in); | |
| 786 | ||
| 787 | const scratch = try s.scratch.reserveMulti( | |
| 788 | s.arena, | |
| 789 | .{ .keys = NullTerminatedString, .types = Type }, | |
| 790 | .{ node.field_len, node.field_len }, | |
| 791 | ); | |
| 792 | defer scratch.deinit(); | |
| 793 | 1060 | |
| 794 | // Fill the first half with keys | |
| 795 | for (scratch.slice(.keys), node.keys(&s.code)) |*dest, src| { | |
| 796 | dest.* = try s.ip.getOrPutString(s.gpa, src.slice(s.code.string_bytes), .no_embedded_nulls); | |
| 797 | } | |
| 1061 | .@"if" => { | |
| 1062 | const data: Teg.If = s.frame.currentPayload(.@"if"); | |
| 1063 | ||
| 1064 | const condition = try s.getCoercedRef(s.lazyNode(.operand), data.cond, .bool, .marked); | |
| 1065 | switch (condition.decode()) { | |
| 1066 | .value => |value| { | |
| 1067 | // if the condition is known at compile time, Sema | |
| 1068 | // will not analyze the branch not taken. | |
| 1069 | assert(value == .bool_true or value == .bool_false); | |
| 1070 | const branch = if (value == .bool_true) data.yes else data.no; | |
| 1071 | switch (branch.decode()) { | |
| 1072 | .value => |v| { | |
| 1073 | // There is no instruction to jump to | |
| 1074 | s.inst_map.add(s.frame.currentIndex(), v.toAir()); | |
| 1075 | s.frame.advance(); | |
| 1076 | }, | |
| 1077 | .index => |block| { | |
| 1078 | const mode = s.frame.mode; | |
| 1079 | try s.pushFrame(); | |
| 1080 | s.frame = Frame.initFromBlock(s, block, mode, .{ | |
| 1081 | .conditional = .comptime_condition, | |
| 1082 | }); | |
| 1083 | }, | |
| 1084 | } | |
| 1085 | }, | |
| 1086 | .index => { | |
| 1087 | try s.ensureAtRuntime(s.lazyNode(.operand)); | |
| 1088 | ||
| 1089 | var yn: [2]Value = undefined; | |
| 1090 | inline for ( | |
| 1091 | .{ data.yes, data.no }, | |
| 1092 | .{ .runtime_yes, .runtime_no }, | |
| 1093 | &yn, | |
| 1094 | ) |teg_ref, state, *out| { | |
| 1095 | switch (teg_ref.decode()) { | |
| 1096 | .value => |v| { | |
| 1097 | // There is no instruction to jump to | |
| 1098 | out.* = v; | |
| 1099 | }, | |
| 1100 | .index => |block| { | |
| 1101 | const mode = s.frame.mode; | |
| 1102 | try s.pushFrame(); | |
| 1103 | s.frame = Frame.initFromBlock(s, block, mode, .{ .conditional = state }); | |
| 1104 | return; | |
| 1105 | }, | |
| 1106 | } | |
| 1107 | } | |
| 798 | 1108 | |
| 799 | // Fill the second half with coerced values. | |
| 800 | for (node.types(&s.code), 0..) |ty, i| { | |
| 801 | scratch.at(.types, i).* = try s.resolveType(in.op(operandArgument(@intCast(i))), ty); | |
| 1109 | // When both branches are values, Sema has nowhere | |
| 1110 | // to jump to, so only one Air instruction is emitted | |
| 1111 | const union_type = s.unionType(yn[0].typeOf(s.ip), yn[1].typeOf(s.ip)); | |
| 1112 | ||
| 1113 | const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yn[0], union_type); | |
| 1114 | const no_coerced = try s.coerceValue(s.lazyNode(.rhs), yn[1], union_type); | |
| 1115 | ||
| 1116 | s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{ | |
| 1117 | .cond = condition, | |
| 1118 | .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{ | |
| 1119 | .yes = yes_coerced.toAir(), | |
| 1120 | .no = no_coerced.toAir(), | |
| 1121 | .union_ty = union_type, | |
| 1122 | }), | |
| 1123 | })); | |
| 1124 | s.frame.advance(); | |
| 1125 | }, | |
| 802 | 1126 | } |
| 803 | ||
| 804 | return try s.makeValueAir(.{ .struct_type = .{ | |
| 805 | .field_keys = scratch.slice(.keys), | |
| 806 | .field_types = scratch.slice(.types), | |
| 807 | } }); | |
| 808 | 1127 | }, |
| 1128 | ||
| 809 | 1129 | .struct_init => { |
| 810 | const node: Teg.StructInit = s.code.instPayload(.struct_init, in); | |
| 1130 | const node: Teg.StructInit = s.frame.currentPayload(.struct_init); | |
| 811 | 1131 | |
| 812 | 1132 | // Ensure we have a valid struct to operate on |
| 813 | const ty = try s.resolveType(in.op(.operand), node.base_type); | |
| 1133 | const ty = try s.getType(s.lazyNode(.operand), node.base_type); | |
| 814 | 1134 | const struct_type = ty.toStructType(s.ip) orelse { |
| 815 | 1135 | return s.fail(.struct_init_invalid_type, .{ |
| 816 | .src = in.op(.operand).lazyNode(s.decl_index), | |
| 1136 | .src = s.lazyNode(.operand), | |
| 817 | 1137 | .ty = ty, |
| 818 | 1138 | }); |
| 819 | 1139 | }; |
| ... | ... | @@ -823,26 +1143,26 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 823 | 1143 | |
| 824 | 1144 | // Allocate scratch space to store the values as they are being prepared. |
| 825 | 1145 | const scratch = try s.scratch.reserve( |
| 826 | s.arena, | |
| 1146 | s.arena(), | |
| 827 | 1147 | Air.Inst.Ref.Optional, |
| 828 | 1148 | struct_type.field_types.len, |
| 829 | 1149 | ); |
| 830 | 1150 | defer scratch.deinit(); |
| 831 | 1151 | @memset(scratch.slice(), .none); |
| 832 | 1152 | |
| 833 | for (node.keys(&s.code), node.inits(&s.code), 0..) |init_key, init_value, i| { | |
| 1153 | for (node.keys(&s.teg), node.inits(&s.teg), 0..) |init_key, init_value, i| { | |
| 834 | 1154 | // The order of `storage` is as ordered by the type, but this |
| 835 | 1155 | // can be in a different ordering than the initialization. We |
| 836 | 1156 | // guarantee the ordering the initializers are visited is in the |
| 837 | 1157 | // order they are declared. |
| 838 | 1158 | const found_index = index: for (struct_type.field_keys, 0..) |decl_key, j| { |
| 839 | if (decl_key.eqlSlice(init_key.slice(s.code.string_bytes), s.ip)) | |
| 1159 | if (decl_key.eqlSlice(init_key.slice(s.teg.string_bytes), s.ip)) | |
| 840 | 1160 | break :index j; |
| 841 | 1161 | } else { |
| 842 | 1162 | has_errors = true; |
| 843 | 1163 | try s.addError(.struct_init_unknown_field, .{ |
| 844 | 1164 | .ty = ty, |
| 845 | .src = in.op(operandArgument(@intCast(i))).lazyNode(s.decl_index), | |
| 1165 | .src = s.lazyNode(.argument(@intCast(i))), | |
| 846 | 1166 | }); |
| 847 | 1167 | continue; |
| 848 | 1168 | }; |
| ... | ... | @@ -850,14 +1170,15 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 850 | 1170 | has_errors = true; |
| 851 | 1171 | try s.addError(.struct_init_duplicate_field, .{ |
| 852 | 1172 | .ty = ty, |
| 853 | .src = in.op(operandArgument(@intCast(i))).lazyNode(s.decl_index), | |
| 1173 | .src = s.lazyNode(.argument(@intCast(i))), | |
| 854 | 1174 | }); |
| 855 | 1175 | continue; |
| 856 | 1176 | } |
| 857 | const coerced = s.resolveCoerceRef( | |
| 858 | in.op(operandArgument(@intCast(i))), | |
| 1177 | const coerced = s.getCoercedRef( | |
| 1178 | s.lazyNode(.argument(@intCast(i))), | |
| 859 | 1179 | init_value, |
| 860 | 1180 | struct_type.field_types[found_index], |
| 1181 | .marked, | |
| 861 | 1182 | ) catch |e| switch (e) { |
| 862 | 1183 | error.AnalysisFail => { |
| 863 | 1184 | // Instead of bailing out with `try`, we will continue the loop. |
| ... | ... | @@ -880,7 +1201,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 880 | 1201 | try s.addError(.struct_init_missing_field, .{ |
| 881 | 1202 | .ty = ty, |
| 882 | 1203 | .field = @intCast(index), |
| 883 | .src = in.op(.main_node).lazyNode(s.decl_index), | |
| 1204 | .src = s.lazyNode(.main_node), | |
| 884 | 1205 | }); |
| 885 | 1206 | i = index + 1; |
| 886 | 1207 | } |
| ... | ... | @@ -901,82 +1222,27 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 901 | 1222 | .field_values = result_storage, |
| 902 | 1223 | } }); |
| 903 | 1224 | } else { |
| 1225 | try s.ensureAtRuntime(s.lazyNode(.main_node)); | |
| 1226 | ||
| 904 | 1227 | // Create a runtime struct instantiation |
| 905 | const value_index = try s.air_extra.appendSlice(s.gpa, scratch.slice()); | |
| 906 | return try s.addInstRef(.struct_init, .{ | |
| 1228 | const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice()); | |
| 1229 | return try s.addRef(.struct_init, .{ | |
| 907 | 1230 | .ty = ty, |
| 908 | 1231 | .field_values = value_index, |
| 909 | 1232 | }); |
| 910 | 1233 | } |
| 911 | 1234 | }, |
| 912 | .array_init => { | |
| 913 | const node: Teg.ArrayInit = s.code.instPayload(.array_init, in); | |
| 914 | ||
| 915 | assert(node.list.len > 0 or node.type_hint.unwrap() != null); | |
| 916 | ||
| 917 | const scratch = try s.scratch.reserve(s.arena, Air.Inst.Ref, node.list.len); | |
| 918 | defer scratch.deinit(); | |
| 919 | ||
| 920 | var item_type: ?Type = if (node.type_hint.unwrap()) |ty| | |
| 921 | try s.resolveType(in.op(.operand), ty) | |
| 922 | else | |
| 923 | null; | |
| 924 | ||
| 925 | var is_constant = true; | |
| 926 | 1235 | |
| 927 | for (node.list.slice(&s.code), 0..) |item_teg_ref, i| { | |
| 928 | const item = try s.resolveRef(item_teg_ref); | |
| 929 | const coerced = if (item_type) |ty| | |
| 930 | try s.coerceRef( | |
| 931 | in.op(operandArgument(@intCast(i))), | |
| 932 | item, | |
| 933 | ty, | |
| 934 | ) | |
| 935 | else brk: { | |
| 936 | item_type = s.typeOfRef(item); | |
| 937 | break :brk item; | |
| 938 | }; | |
| 939 | ||
| 940 | if (coerced.kind == .index) is_constant = false; | |
| 941 | ||
| 942 | scratch.at(i).* = coerced; | |
| 943 | } | |
| 944 | ||
| 945 | const ty = try s.makeType(.{ .array_type = .{ | |
| 946 | .item_type = item_type orelse unreachable, | |
| 947 | } }); | |
| 948 | ||
| 949 | if (is_constant) { | |
| 950 | // Convert all items in storage into a Value type | |
| 951 | const result_storage = scratch.sliceReinterpret(Value); | |
| 952 | for (scratch.slice(), scratch.sliceReinterpret(Value)) |ref, *value| { | |
| 953 | value.* = ref.decode().value; | |
| 954 | } | |
| 955 | return try s.makeValueAir(.{ .array_value = .{ | |
| 956 | .type = ty, | |
| 957 | .items = result_storage, | |
| 958 | } }); | |
| 959 | } else { | |
| 960 | // Create a runtime array instantiation | |
| 961 | return try s.addInstRef(.array_init, .{ | |
| 962 | .ty = ty, | |
| 963 | .list = try s.air_extra.append(s.gpa, Air.RefList, .{ | |
| 964 | .start = try s.air_extra.appendSlice(s.gpa, scratch.slice()), | |
| 965 | .len = node.list.len, | |
| 966 | }), | |
| 967 | }); | |
| 968 | } | |
| 969 | }, | |
| 970 | 1236 | .array_type => { |
| 971 | const un: Teg.RefNode = s.code.instData(.array_type, in); | |
| 972 | const operand = try s.resolveType(in.op(.operand), un.operand); | |
| 1237 | const un: Teg.RefNode = s.frame.currentData(.array_type); | |
| 1238 | const operand = try s.getType(s.lazyNode(.operand), un.operand); | |
| 973 | 1239 | return s.makeValueAir(.{ .array_type = .{ .item_type = operand } }); |
| 974 | 1240 | }, |
| 975 | 1241 | |
| 976 | 1242 | .result_type_argument => { |
| 977 | const result: Teg.ResultTypeArgument = s.code.instData(.result_type_argument, in); | |
| 1243 | const result: Teg.ResultTypeArgument = s.frame.currentData(.result_type_argument); | |
| 978 | 1244 | |
| 979 | const operand = try s.resolveRef(result.call_base); | |
| 1245 | const operand = s.getRef(result.call_base, .no_mark); | |
| 980 | 1246 | const ty = s.typeOfRef(operand); |
| 981 | 1247 | |
| 982 | 1248 | const func: InternPool.Key.FunctionType = ty.toFunctionPrototype(s.ip) orelse |
| ... | ... | @@ -989,9 +1255,9 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 989 | 1255 | }, |
| 990 | 1256 | |
| 991 | 1257 | .result_type_array_value => { |
| 992 | const result: Teg.ResultTypeArrayValue = s.code.instData(.result_type_array_value, in); | |
| 1258 | const result: Teg.ResultTypeArrayValue = s.frame.currentData(.result_type_array_value); | |
| 993 | 1259 | |
| 994 | const operand = try s.resolveValue(result.type); | |
| 1260 | const operand = try s.getValue(s.lazyNode(.main_node), result.type); | |
| 995 | 1261 | const ty = operand.toType(s.ip) orelse |
| 996 | 1262 | return Value.opaque_type.toAir(); |
| 997 | 1263 | |
| ... | ... | @@ -1002,9 +1268,9 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 1002 | 1268 | }, |
| 1003 | 1269 | |
| 1004 | 1270 | .result_type_tuple_field => { |
| 1005 | const result: Teg.ResultTypeTupleField = s.code.instData(.result_type_tuple_field, in); | |
| 1271 | const result: Teg.ResultTypeTupleField = s.frame.currentData(.result_type_tuple_field); | |
| 1006 | 1272 | |
| 1007 | const operand = try s.resolveValue(result.type); | |
| 1273 | const operand = try s.getValue(s.lazyNode(.main_node), result.type); | |
| 1008 | 1274 | const ty = operand.toType(s.ip) orelse |
| 1009 | 1275 | return Value.opaque_type.toAir(); |
| 1010 | 1276 | |
| ... | ... | @@ -1014,16 +1280,16 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 1014 | 1280 | }, |
| 1015 | 1281 | |
| 1016 | 1282 | .result_type_struct_field => { |
| 1017 | const result: Teg.ResultTypeStructField = s.code.instData(.result_type_struct_field, in); | |
| 1283 | const result: Teg.ResultTypeStructField = s.frame.currentData(.result_type_struct_field); | |
| 1018 | 1284 | |
| 1019 | const operand = try s.resolveValue(result.type); | |
| 1285 | const operand = try s.getValue(s.lazyNode(.main_node), result.type); | |
| 1020 | 1286 | const ty = operand.toType(s.ip) orelse |
| 1021 | 1287 | return Value.opaque_type.toAir(); |
| 1022 | 1288 | |
| 1023 | 1289 | const struct_type = ty.toStructType(s.ip) orelse |
| 1024 | 1290 | return Value.opaque_type.toAir(); |
| 1025 | 1291 | |
| 1026 | const to_match = result.field.slice(s.code.string_bytes); | |
| 1292 | const to_match = result.field.slice(s.teg.string_bytes); | |
| 1027 | 1293 | for (struct_type.field_keys, struct_type.field_types) |k, v| { |
| 1028 | 1294 | if (k.eqlSlice(to_match, s.ip)) { |
| 1029 | 1295 | return v.value().toAir(); |
| ... | ... | @@ -1033,148 +1299,193 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 1033 | 1299 | return Value.opaque_type.toAir(); |
| 1034 | 1300 | }, |
| 1035 | 1301 | |
| 1036 | else => |tag| std.debug.panic("TODO: analyze of .{s}", .{@tagName(tag)}), | |
| 1302 | else => |unhandled_tag| std.debug.panic("TODO: analyze instruction .{s}", .{@tagName(unhandled_tag)}), | |
| 1037 | 1303 | } |
| 1038 | 1304 | } |
| 1039 | 1305 | |
| 1040 | fn markInst(s: *Sema, inst: Air.Inst.Index) void { | |
| 1041 | const flags: *Air.Flags = &s.air_instructions.items(.flags)[@intFromEnum(inst)]; | |
| 1042 | flags.uses = flags.uses.increment(); | |
| 1306 | fn emitRuntimeBlock( | |
| 1307 | s: *Sema, | |
| 1308 | from: Teg.Inst.Index, | |
| 1309 | break_operand: Air.Inst.Ref, | |
| 1310 | mark: core.U32Stack.Mark, | |
| 1311 | ) !void { | |
| 1312 | s.inst_map.add( | |
| 1313 | from, | |
| 1314 | if (mark.len() == 0) brk: { | |
| 1315 | assert(break_operand.kind == .value); | |
| 1316 | break :brk break_operand; | |
| 1317 | } else brk: { | |
| 1318 | assert(s.frame.mode == .run_time); | |
| 1319 | const func = &s.func.?; | |
| 1320 | const idx = func.extra.list.items.len; | |
| 1321 | const block_instructions = mark.slice(u32); | |
| 1322 | try func.extra.list.ensureUnusedCapacity(s.gpa, block_instructions.len); | |
| 1323 | const block = try s.addIndexUntracked(.block, .{ | |
| 1324 | .start = @intCast(idx), | |
| 1325 | .len = @intCast(block_instructions.len + 1), | |
| 1326 | }); | |
| 1327 | const brk_instruction = try s.addIndexUntracked(.@"break", .{ | |
| 1328 | .block = block, | |
| 1329 | .value = break_operand, | |
| 1330 | }); | |
| 1331 | func.extra.list.appendSliceAssumeCapacity(block_instructions); | |
| 1332 | func.extra.list.appendAssumeCapacity(@intFromEnum(brk_instruction)); | |
| 1333 | break :brk block.toRef(); | |
| 1334 | }, | |
| 1335 | ); | |
| 1336 | mark.deinit(); | |
| 1043 | 1337 | } |
| 1044 | 1338 | |
| 1045 | fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref { | |
| 1046 | const t = tracer.trace(@src()); | |
| 1047 | defer t.end(); | |
| 1048 | ||
| 1049 | const scratch = s.scratch.mark(); | |
| 1050 | defer scratch.deinit(); | |
| 1051 | ||
| 1052 | const visited = s.resolveRef(in) catch |err| switch (err) { | |
| 1053 | error.HitNoReturn => Value.void.toAir(), | |
| 1054 | else => return err, | |
| 1055 | }; | |
| 1339 | const BinaryOp = enum { | |
| 1340 | add, | |
| 1341 | sub, | |
| 1342 | mul, | |
| 1343 | div, | |
| 1344 | lt, | |
| 1345 | gt, | |
| 1346 | eq, | |
| 1347 | }; | |
| 1056 | 1348 | |
| 1057 | if (scratch.len() == 0 and visited.kind == .value) { | |
| 1058 | return visited; | |
| 1349 | fn binTypeResolution(s: *Sema, src: Teg.Inst.Index, op: BinaryOp, lhs: Type, rhs: Type) !Type { | |
| 1350 | if (lhs == .@"opaque") { | |
| 1351 | return if (rhs == .numeric_literal) .float else rhs; | |
| 1352 | } | |
| 1353 | if (rhs == .@"opaque") { | |
| 1354 | return if (lhs == .numeric_literal) .float else lhs; | |
| 1059 | 1355 | } |
| 1060 | 1356 | |
| 1061 | if (visited.kind == .index and | |
| 1062 | s.air_instructions.items(.tag)[@intFromEnum(visited.decode().index)] == .block) | |
| 1063 | { | |
| 1064 | return visited; | |
| 1357 | switch (lhs) { | |
| 1358 | .string => if (op == .add) switch (rhs) { | |
| 1359 | .string => return .string, | |
| 1360 | else => {}, | |
| 1361 | }, | |
| 1362 | .numeric_literal => switch (rhs) { | |
| 1363 | .int32, | |
| 1364 | .float, | |
| 1365 | .numeric_literal, | |
| 1366 | => return rhs, | |
| 1367 | else => {}, | |
| 1368 | }, | |
| 1369 | .int32 => switch (rhs) { | |
| 1370 | .int32 => return .int32, | |
| 1371 | .numeric_literal => return .int32, | |
| 1372 | else => {}, | |
| 1373 | }, | |
| 1374 | .float => switch (rhs) { | |
| 1375 | .int32 => return .float, | |
| 1376 | .numeric_literal => return .float, | |
| 1377 | .float => return .float, | |
| 1378 | else => {}, | |
| 1379 | }, | |
| 1380 | else => {}, | |
| 1065 | 1381 | } |
| 1066 | 1382 | |
| 1067 | const block = try s.addBlock(); | |
| 1068 | _ = try s.addInstRef(.@"break", .{ | |
| 1069 | .block_ref = block.block_index, | |
| 1070 | .value = visited, | |
| 1383 | return s.fail(.bin_op_mismatch, .{ | |
| 1384 | .src = src.op(.main_node).lazyNode(s.techDebtDeclIndex()), | |
| 1385 | .lhs = lhs, | |
| 1386 | .rhs = rhs, | |
| 1071 | 1387 | }); |
| 1072 | return (try block.finish(s, scratch)).toRef(); | |
| 1073 | 1388 | } |
| 1074 | 1389 | |
| 1075 | fn resolveRefExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) Error!Air.Inst.Ref { | |
| 1076 | return switch (ref.decode()) { | |
| 1077 | .value => |value| value.toAir(), | |
| 1078 | .index => |index| { | |
| 1079 | if (s.inst_map.get(index)) |air| { | |
| 1080 | if (mark) { | |
| 1081 | switch (air.decode()) { | |
| 1082 | .index => |i| s.markInst(i), | |
| 1083 | else => {}, | |
| 1084 | } | |
| 1085 | } | |
| 1086 | ||
| 1087 | return air; | |
| 1088 | } | |
| 1390 | fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value { | |
| 1391 | const left_ty = lhs.typeOf(s.ip); | |
| 1392 | assert(rhs.typeOf(s.ip) == left_ty); | |
| 1089 | 1393 | |
| 1090 | return s.analyzeCode(index); | |
| 1091 | }, | |
| 1092 | }; | |
| 1093 | } | |
| 1394 | switch (op) { | |
| 1395 | .add => switch (left_ty) { | |
| 1396 | .numeric_literal, .float => { | |
| 1397 | const l = lhs.toFloat(s.ip) orelse | |
| 1398 | unreachable; // must be coerced | |
| 1399 | const r = rhs.toFloat(s.ip) orelse | |
| 1400 | unreachable; // must be coerced | |
| 1094 | 1401 | |
| 1095 | fn resolveRef(s: *Sema, ref: Teg.Inst.Ref) Error!Air.Inst.Ref { | |
| 1096 | return resolveRefExtra(s, ref, true); | |
| 1097 | } | |
| 1402 | const result = l + r; | |
| 1098 | 1403 | |
| 1099 | fn resolveValue(s: *Sema, ref: Teg.Inst.Ref) !Value { | |
| 1100 | const index = switch (ref.decode()) { | |
| 1101 | .value => |value| return value, | |
| 1102 | .index => |index| index, | |
| 1103 | }; | |
| 1404 | return try s.makeValue(if (left_ty == .numeric_literal) | |
| 1405 | .{ .comptime_float = result } | |
| 1406 | else | |
| 1407 | .{ .float = result }); | |
| 1408 | }, | |
| 1409 | .int32 => { | |
| 1410 | const l = lhs.toInt32(s.ip) orelse | |
| 1411 | unreachable; // must be coerced | |
| 1412 | const r = rhs.toInt32(s.ip) orelse | |
| 1413 | unreachable; // must be coerced | |
| 1104 | 1414 | |
| 1105 | if (s.inst_map.get(index)) |air| { | |
| 1106 | switch (air.decode()) { | |
| 1107 | .value => |value| return value, | |
| 1108 | else => {}, | |
| 1109 | } | |
| 1110 | } | |
| 1415 | const result = std.math.add(i32, l, r) catch | |
| 1416 | @panic("TODO: out of range"); | |
| 1111 | 1417 | |
| 1112 | switch (s.code.instTag(index)) { | |
| 1113 | .string => { | |
| 1114 | const string: Teg.SlicedString = s.code.instPayload(.string, index); | |
| 1115 | return s.makeValue(.{ .string = string.slice(s.code.string_bytes) }); | |
| 1418 | return try s.makeValue(.{ .int32 = result }); | |
| 1419 | }, | |
| 1420 | .string => { | |
| 1421 | const l = lhs.asString(s.ip).toSlice(s.ip); | |
| 1422 | const r = rhs.asString(s.ip).toSlice(s.ip); | |
| 1423 | var sfa = std.heap.stackFallback(4096, s.arena()); | |
| 1424 | const alloc = sfa.get(); | |
| 1425 | const joined = try std.mem.concat(alloc, u8, &.{ l, r }); | |
| 1426 | defer alloc.free(joined); | |
| 1427 | return try s.makeValue(.{ .string = joined }); | |
| 1428 | }, | |
| 1429 | else => unreachable, | |
| 1116 | 1430 | }, |
| 1431 | inline .sub, .mul, .div => |tag| switch (left_ty) { | |
| 1432 | .numeric_literal, .float => { | |
| 1433 | const l = lhs.toFloat(s.ip) orelse | |
| 1434 | unreachable; // must be coerced | |
| 1435 | const r = rhs.toFloat(s.ip) orelse | |
| 1436 | unreachable; // must be coerced | |
| 1117 | 1437 | |
| 1118 | .block => { | |
| 1119 | // Currently only one pattern is supported: | |
| 1120 | // | |
| 1121 | // %2 = block [ | |
| 1122 | // %1 = load_decl X | |
| 1123 | // %3 = break(%3, %2) | |
| 1124 | // ] | |
| 1125 | // | |
| 1126 | // This is to support function arguments with structs. | |
| 1127 | // The rest of this will be implemented in https://github.com/paperdave/wl/issues/44 | |
| 1128 | special_cased: { | |
| 1129 | const index_list: Teg.IndexList = s.code.instPayload(.block, index); | |
| 1130 | if (index_list.len != 2) break :special_cased; | |
| 1131 | const indexes = index_list.slice(&s.code); | |
| 1132 | const load_inst = indexes[0]; | |
| 1133 | const break_inst = indexes[1]; | |
| 1134 | ||
| 1135 | if (s.code.instTag(break_inst) != .@"break") break :special_cased; | |
| 1136 | const break_data: Teg.Break = s.code.instPayload(.@"break", break_inst); | |
| 1137 | if (break_data.operand != load_inst.toRef()) break :special_cased; | |
| 1138 | if (break_data.block != index) break :special_cased; | |
| 1139 | ||
| 1140 | if (s.code.instTag(load_inst) != .load_decl) break :special_cased; | |
| 1141 | ||
| 1142 | const request: Teg.StrNode = s.code.instData(.load_decl, load_inst); | |
| 1143 | const decl_name = try s.ip.getOrPutString( | |
| 1144 | s.gpa, | |
| 1145 | request.string.slice(s.code.string_bytes), | |
| 1146 | .no_embedded_nulls, | |
| 1147 | ); | |
| 1148 | const decl_index = s.lookupDecl(decl_name) orelse { | |
| 1149 | std.debug.print("did not find {s}\n", .{decl_name.toSlice(s.ip)}); | |
| 1150 | return error.AnalysisFail; | |
| 1438 | const result = switch (tag) { | |
| 1439 | .sub => l - r, | |
| 1440 | .mul => l * r, | |
| 1441 | .div => l / r, | |
| 1442 | else => @compileError(unreachable), | |
| 1151 | 1443 | }; |
| 1152 | return try s.resolveDecl(load_inst.op(.main_node), decl_index); | |
| 1153 | } | |
| 1154 | ||
| 1155 | std.debug.panic("TODO: advanced comptime interpreter", .{}); | |
| 1156 | }, | |
| 1157 | 1444 | |
| 1158 | else => |tag| std.debug.panic("TODO: comptime analyze of .{s} {d}", .{ @tagName(tag), @intFromEnum(index) }), | |
| 1159 | } | |
| 1160 | } | |
| 1445 | return try s.makeValue(if (left_ty == .numeric_literal) | |
| 1446 | .{ .comptime_float = result } | |
| 1447 | else | |
| 1448 | .{ .float = result }); | |
| 1449 | }, | |
| 1450 | .int32 => { | |
| 1451 | const l = lhs.toInt32(s.ip) orelse | |
| 1452 | unreachable; // must be coerced | |
| 1453 | const r = rhs.toInt32(s.ip) orelse | |
| 1454 | unreachable; // must be coerced | |
| 1161 | 1455 | |
| 1162 | fn resolveCoerceValue(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref, ty: Type) !Value { | |
| 1163 | const any = try s.resolveValue(ref); | |
| 1164 | return try s.coerceValue(src, any, ty); | |
| 1165 | } | |
| 1456 | const result = switch (tag) { | |
| 1457 | .sub => std.math.sub(i32, l, r), | |
| 1458 | .mul => std.math.mul(i32, l, r), | |
| 1459 | .div => return try s.makeValue(.{ .float = @as(f64, @floatFromInt(l)) / @as(f64, @floatFromInt(r)) }), | |
| 1460 | else => @compileError(unreachable), | |
| 1461 | } catch @panic("TODO: out of range"); | |
| 1166 | 1462 | |
| 1167 | fn resolveCoerceRef(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref, ty: Type) !Air.Inst.Ref { | |
| 1168 | const any = try s.resolveRef(ref); | |
| 1169 | return try s.coerceRef(src, any, ty); | |
| 1170 | } | |
| 1463 | return try s.makeValue(.{ .int32 = result }); | |
| 1464 | }, | |
| 1465 | else => unreachable, | |
| 1466 | }, | |
| 1467 | inline .lt, .gt => |tag| { | |
| 1468 | const l = lhs.toFloat(s.ip) orelse | |
| 1469 | unreachable; // must be coerced | |
| 1470 | const r = rhs.toFloat(s.ip) orelse | |
| 1471 | unreachable; // must be coerced | |
| 1171 | 1472 | |
| 1172 | fn resolveType(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref) !Type { | |
| 1173 | return (try s.resolveCoerceValue(src, ref, .type)).toTypeUnsafe(s.ip); | |
| 1473 | const result = switch (tag) { | |
| 1474 | .lt => l < r, | |
| 1475 | .gt => l > r, | |
| 1476 | else => @compileError(unreachable), | |
| 1477 | }; | |
| 1478 | return if (result) .bool_true else .bool_false; | |
| 1479 | }, | |
| 1480 | .eq => { | |
| 1481 | const result = lhs == rhs; | |
| 1482 | return if (result) .bool_true else .bool_false; | |
| 1483 | }, | |
| 1484 | } | |
| 1174 | 1485 | } |
| 1175 | 1486 | |
| 1176 | 1487 | /// Coerce `value` runtime ref to `coerce_type`, and emit an error at `src`'s location |
| 1177 | fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref { | |
| 1488 | fn coerceRef(s: *Sema, src: LazyNode, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref { | |
| 1178 | 1489 | const t = tracer.traceFmt(@src(), "{} to {}", .{ ref.dump(s.ip), to.dump(s.ip) }); |
| 1179 | 1490 | defer t.end(); |
| 1180 | 1491 | |
| ... | ... | @@ -1182,9 +1493,6 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref { |
| 1182 | 1493 | if (from == to) { |
| 1183 | 1494 | return ref; |
| 1184 | 1495 | } |
| 1185 | if (from == .noreturn) { | |
| 1186 | return error.HitNoReturn; | |
| 1187 | } | |
| 1188 | 1496 | if (from == .@"opaque") { |
| 1189 | 1497 | return ref; |
| 1190 | 1498 | } |
| ... | ... | @@ -1192,6 +1500,9 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref { |
| 1192 | 1500 | .value => |v| return (try s.coerceValue(src, v, to)).toAir(), |
| 1193 | 1501 | .index => |index| index, |
| 1194 | 1502 | }; |
| 1503 | if (s.frame.mode == .compile_time) { | |
| 1504 | return s.fail(.not_comptime_known, .{ .src = src }); | |
| 1505 | } | |
| 1195 | 1506 | switch (to) { |
| 1196 | 1507 | .@"opaque" => { |
| 1197 | 1508 | // |
| ... | ... | @@ -1212,7 +1523,7 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref { |
| 1212 | 1523 | .string => { |
| 1213 | 1524 | // |
| 1214 | 1525 | }, |
| 1215 | .noreturn => { | |
| 1526 | .never => { | |
| 1216 | 1527 | // |
| 1217 | 1528 | }, |
| 1218 | 1529 | .type => { |
| ... | ... | @@ -1230,7 +1541,7 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref { |
| 1230 | 1541 | } |
| 1231 | 1542 | |
| 1232 | 1543 | /// Same as `coerceRef` but on a compile-time known value |
| 1233 | fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value { | |
| 1544 | fn coerceValue(s: *Sema, src: LazyNode, value: Value, to: Type) Error!Value { | |
| 1234 | 1545 | const from = value.typeOf(s.ip); |
| 1235 | 1546 | if (from == to) { |
| 1236 | 1547 | return value; |
| ... | ... | @@ -1249,7 +1560,7 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value { |
| 1249 | 1560 | if (!core.inInt32Range(val)) { |
| 1250 | 1561 | return s.fail(.comptime_float_to_int, .{ |
| 1251 | 1562 | .val = val, |
| 1252 | .src = s.lazyNode(src), | |
| 1563 | .src = src, | |
| 1253 | 1564 | }); |
| 1254 | 1565 | } |
| 1255 | 1566 | |
| ... | ... | @@ -1272,8 +1583,8 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value { |
| 1272 | 1583 | .string => { |
| 1273 | 1584 | // TODO: |
| 1274 | 1585 | }, |
| 1275 | .noreturn => { | |
| 1276 | // nothing coerces to `noreturn` | |
| 1586 | .never => { | |
| 1587 | // nothing coerces to `never` | |
| 1277 | 1588 | }, |
| 1278 | 1589 | .numeric_literal => { |
| 1279 | 1590 | if (value.toFloat(s.ip)) |float| { |
| ... | ... | @@ -1299,7 +1610,7 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value { |
| 1299 | 1610 | return s.coercionError(src, from, to); |
| 1300 | 1611 | } |
| 1301 | 1612 | |
| 1302 | fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.Ref { | |
| 1613 | fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref { | |
| 1303 | 1614 | switch (ref.decode()) { |
| 1304 | 1615 | .value => |v| { |
| 1305 | 1616 | const ty = v.typeOf(s.ip); |
| ... | ... | @@ -1307,9 +1618,9 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R |
| 1307 | 1618 | .numeric_literal => { |
| 1308 | 1619 | const float = v.toFloat(s.ip) orelse unreachable; |
| 1309 | 1620 | |
| 1310 | if (core.inInt32Range(float)) { | |
| 1621 | if (!core.inInt32Range(float)) { | |
| 1311 | 1622 | try s.report.warn(.numeric_int_var_ambiguity, .{ |
| 1312 | .src = s.lazyNode(src), | |
| 1623 | .src = src, | |
| 1313 | 1624 | }); |
| 1314 | 1625 | } |
| 1315 | 1626 | |
| ... | ... | @@ -1319,22 +1630,22 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R |
| 1319 | 1630 | }, |
| 1320 | 1631 | .numeric_literal_array => { |
| 1321 | 1632 | try s.report.warn(.numeric_int_array_var_ambiguity, .{ |
| 1322 | .src = s.lazyNode(src), | |
| 1633 | .src = src, | |
| 1323 | 1634 | }); |
| 1324 | 1635 | return (try s.coerceArrayValue(src, v, .float)).toAir(); |
| 1325 | 1636 | }, |
| 1326 | .noreturn_array => { | |
| 1637 | .never_array => { | |
| 1327 | 1638 | if (v.valueData(s.ip, .array_value).get(s.ip).items_len != 0) { |
| 1328 | 1639 | return s.fail(.runtime_incompatible_type, .{ |
| 1329 | .src = s.lazyNode(src), | |
| 1640 | .src = src, | |
| 1330 | 1641 | .type = ty, |
| 1331 | 1642 | }); |
| 1332 | 1643 | } |
| 1333 | 1644 | }, |
| 1334 | 1645 | .type, |
| 1335 | .noreturn, | |
| 1646 | .never, | |
| 1336 | 1647 | => return s.fail(.runtime_incompatible_type, .{ |
| 1337 | .src = s.lazyNode(src), | |
| 1648 | .src = src, | |
| 1338 | 1649 | .type = ty, |
| 1339 | 1650 | }), |
| 1340 | 1651 | .@"opaque" => @panic("TODO: represent comptime-known opaques better"), |
| ... | ... | @@ -1347,7 +1658,7 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R |
| 1347 | 1658 | => {}, |
| 1348 | 1659 | _ => if (!ty.hasPossibleRepresentation(s.ip)) { |
| 1349 | 1660 | return s.fail(.runtime_incompatible_type, .{ |
| 1350 | .src = s.lazyNode(src), | |
| 1661 | .src = src, | |
| 1351 | 1662 | .type = ty, |
| 1352 | 1663 | }); |
| 1353 | 1664 | }, |
| ... | ... | @@ -1357,7 +1668,7 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R |
| 1357 | 1668 | const ty = s.typeOfRef(ref); |
| 1358 | 1669 | if (!ty.hasPossibleRepresentation(s.ip)) { |
| 1359 | 1670 | return s.fail(.runtime_incompatible_type, .{ |
| 1360 | .src = s.lazyNode(src), | |
| 1671 | .src = src, | |
| 1361 | 1672 | .type = ty, |
| 1362 | 1673 | }); |
| 1363 | 1674 | } |
| ... | ... | @@ -1366,89 +1677,27 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R |
| 1366 | 1677 | return ref; |
| 1367 | 1678 | } |
| 1368 | 1679 | |
| 1369 | fn ensureValueDefinedRepresentation(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.Ref { | |
| 1680 | fn ensureValueDefinedRepresentation(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref { | |
| 1370 | 1681 | const runtime = try s.ensureValueRuntime(src, ref); |
| 1371 | 1682 | const ty = s.typeOfRef(runtime); |
| 1372 | 1683 | if (!ty.hasDefinedRepresentation(s.ip)) { |
| 1373 | 1684 | return s.fail(.type_no_defined_representation, .{ |
| 1374 | .src = s.lazyNode(src), | |
| 1685 | .src = src, | |
| 1375 | 1686 | .type = ty, |
| 1376 | 1687 | }); |
| 1377 | 1688 | } |
| 1378 | 1689 | return runtime; |
| 1379 | 1690 | } |
| 1380 | 1691 | |
| 1381 | fn coerceArrayValue(s: *Sema, src: InstOp, value: Value, item_type: Type) !Value { | |
| 1382 | const array: InternPool.Tag.ArrayValue, var trail = value.valueData(s.ip, .array_value).getTrail(s.ip); | |
| 1383 | ||
| 1384 | const scratch = try s.scratch.reserve(s.arena, Value, array.items_len); | |
| 1385 | defer scratch.deinit(); | |
| 1386 | ||
| 1387 | const from = trail.readValueSlice(array.items_len); | |
| 1388 | ||
| 1389 | for (0..from.len) |i| { | |
| 1390 | scratch.at(i).* = try s.coerceValue(src, from.get(s.ip)[i], item_type); | |
| 1391 | } | |
| 1392 | ||
| 1393 | return try s.makeValue(.{ .array_value = .{ | |
| 1394 | .type = try s.makeType(.{ .array_type = .{ .item_type = item_type } }), | |
| 1395 | .items = scratch.slice(), | |
| 1396 | } }); | |
| 1397 | } | |
| 1398 | ||
| 1399 | fn resolveDecl(s: *Sema, src: InstOp, decl_index: Decl.Index) Error!Value { | |
| 1400 | const decl = s.ip.declPtr(decl_index); | |
| 1401 | switch (decl.analysis) { | |
| 1402 | .unreferenced => { | |
| 1403 | decl.analysis = .in_progress; | |
| 1404 | ||
| 1405 | // TODO: make this short-circuit the current analysis, move this analyze | |
| 1406 | // job into a "deferred" list, and then make this thread analyze something else | |
| 1407 | ||
| 1408 | var arena = std.heap.ArenaAllocator.init(s.gpa); | |
| 1409 | defer arena.deinit(); | |
| 1410 | ||
| 1411 | var todo: Report = undefined; | |
| 1412 | var sema: Sema = .{ | |
| 1413 | .comp = s.comp, | |
| 1414 | .gpa = s.gpa, | |
| 1415 | .ip = s.ip, | |
| 1416 | .arena = arena.allocator(), | |
| 1417 | .code = try s.ip.namespacePtr(decl.src_namespace).file_scope.getTeg(s.comp, &todo), | |
| 1418 | .decl = decl, | |
| 1419 | .decl_index = decl_index, | |
| 1420 | .namespace_index = decl.src_namespace, | |
| 1421 | }; | |
| 1422 | ||
| 1423 | try sema.init(); | |
| 1424 | defer sema.deinit(); | |
| 1425 | const value = try sema.analyzeDeclaration(); | |
| 1426 | try sema.finalize(); | |
| 1427 | ||
| 1428 | decl.analysis = .complete; | |
| 1429 | ||
| 1430 | return value; | |
| 1431 | }, | |
| 1432 | .in_progress => { | |
| 1433 | return s.fail(.dependency_loop, .{ | |
| 1434 | .src = s.lazyNode(src), | |
| 1435 | }); | |
| 1436 | }, | |
| 1437 | else => { | |
| 1438 | return decl.value(); | |
| 1439 | }, | |
| 1440 | } | |
| 1441 | } | |
| 1442 | ||
| 1443 | 1692 | fn unionType(s: *Sema, a: Type, b: Type) Type { |
| 1444 | 1693 | if (a == b) { |
| 1445 | 1694 | return a; |
| 1446 | 1695 | } |
| 1447 | 1696 | |
| 1448 | if (a == .noreturn) | |
| 1697 | if (a == .never) | |
| 1449 | 1698 | return b; |
| 1450 | 1699 | |
| 1451 | if (b == .noreturn) | |
| 1700 | if (b == .never) | |
| 1452 | 1701 | return a; |
| 1453 | 1702 | |
| 1454 | 1703 | std.debug.panic( |
| ... | ... | @@ -1461,198 +1710,208 @@ fn unionType(s: *Sema, a: Type, b: Type) Type { |
| 1461 | 1710 | // return s.ip.get(s.gpa, .{ .@"union" = &.{ a, b } }); |
| 1462 | 1711 | } |
| 1463 | 1712 | |
| 1464 | const BinaryOp = enum { | |
| 1465 | add, | |
| 1466 | sub, | |
| 1467 | mul, | |
| 1468 | div, | |
| 1469 | lt, | |
| 1470 | gt, | |
| 1471 | eq, | |
| 1472 | }; | |
| 1713 | fn coerceArrayValue(s: *Sema, src: LazyNode, value: Value, item_type: Type) !Value { | |
| 1714 | const array: InternPool.Tag.ArrayValue, var trail = value.valueData(s.ip, .array_value).getTrail(s.ip); | |
| 1473 | 1715 | |
| 1474 | fn binTypeResolution(s: *Sema, src: Teg.Inst.Index, op: BinaryOp, lhs: Type, rhs: Type) !Type { | |
| 1475 | if (lhs == .@"opaque") { | |
| 1476 | return if (rhs == .numeric_literal) .float else rhs; | |
| 1716 | const scratch = try s.scratch.reserve(s.arena(), Value, array.items_len); | |
| 1717 | defer scratch.deinit(); | |
| 1718 | ||
| 1719 | const from = trail.readValueSlice(array.items_len); | |
| 1720 | ||
| 1721 | for (0..from.len) |i| { | |
| 1722 | scratch.at(i).* = try s.coerceValue(src, from.get(s.ip)[i], item_type); | |
| 1477 | 1723 | } |
| 1478 | if (rhs == .@"opaque") { | |
| 1479 | return if (lhs == .numeric_literal) .float else lhs; | |
| 1724 | ||
| 1725 | return try s.makeValue(.{ .array_value = .{ | |
| 1726 | .type = try s.makeType(.{ .array_type = .{ .item_type = item_type } }), | |
| 1727 | .items = scratch.slice(), | |
| 1728 | } }); | |
| 1729 | } | |
| 1730 | ||
| 1731 | fn lookupDecl(s: *Sema, name: NullTerminatedString) InternPool.DeclIndex { | |
| 1732 | var nsi = s.ns_index; | |
| 1733 | while (true) { | |
| 1734 | const ns = s.ip.namespacePtr(nsi); | |
| 1735 | if (ns.lookupDirect(s.comp, name)) |decl_id| { | |
| 1736 | return decl_id; | |
| 1737 | } | |
| 1738 | nsi = ns.parent.unwrap() orelse | |
| 1739 | unreachable; // transform ensures all identifiers get resolved | |
| 1480 | 1740 | } |
| 1741 | } | |
| 1481 | 1742 | |
| 1482 | switch (lhs) { | |
| 1483 | .string => if (op == .add) switch (rhs) { | |
| 1484 | .string => return .string, | |
| 1485 | else => {}, | |
| 1486 | }, | |
| 1487 | .numeric_literal => switch (rhs) { | |
| 1488 | .int32, | |
| 1489 | .float, | |
| 1490 | .numeric_literal, | |
| 1491 | => return rhs, | |
| 1492 | else => {}, | |
| 1743 | fn getDecl(sema: *Sema, decl_index: Decl.Index) !Value { | |
| 1744 | if (sema.owner.kind == .decl) { | |
| 1745 | if (decl_index == sema.owner.unwrap().decl) { | |
| 1746 | return sema.ip.declPtr(decl_index).value(); | |
| 1747 | } | |
| 1748 | } | |
| 1749 | const decl_data = sema.ip.declPtr(decl_index); | |
| 1750 | try sema.declareDependency(.{ .decl_val = decl_index }); | |
| 1751 | const au: AnalysisUnit = .wrap(.{ .decl = decl_index }); | |
| 1752 | switch (decl_data.analysis) { | |
| 1753 | .unreferenced => { | |
| 1754 | try sema.comp.jobs.writeItem(.{ .analysis = .{ .au = au, .first_blocked_sema = sema } }); | |
| 1755 | return error.Interrupt; | |
| 1493 | 1756 | }, |
| 1494 | .int32 => switch (rhs) { | |
| 1495 | .int32 => return .int32, | |
| 1496 | .numeric_literal => return .int32, | |
| 1497 | else => {}, | |
| 1757 | .waiting, .in_progress => { | |
| 1758 | const other_sema = sema.comp.ongoing_analysis.get(au) orelse unreachable; | |
| 1759 | try other_sema.blocked_semas.append(sema.gpa, sema); | |
| 1760 | return error.Interrupt; | |
| 1498 | 1761 | }, |
| 1499 | .float => switch (rhs) { | |
| 1500 | .int32 => return .float, | |
| 1501 | .numeric_literal => return .float, | |
| 1502 | .float => return .float, | |
| 1503 | else => {}, | |
| 1762 | .complete => { | |
| 1763 | return decl_data.value(); | |
| 1504 | 1764 | }, |
| 1505 | else => {}, | |
| 1506 | 1765 | } |
| 1766 | } | |
| 1507 | 1767 | |
| 1508 | return s.fail(.bin_op_mismatch, .{ | |
| 1509 | .src = s.nodeLoc(src, .main_node), | |
| 1510 | .lhs = lhs, | |
| 1511 | .rhs = rhs, | |
| 1512 | }); | |
| 1768 | fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void { | |
| 1769 | try sema.ip.addDependency(sema.gpa, sema.owner, dependee); | |
| 1513 | 1770 | } |
| 1514 | 1771 | |
| 1515 | fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value { | |
| 1516 | const left_ty = lhs.typeOf(s.ip); | |
| 1517 | assert(rhs.typeOf(s.ip) == left_ty); | |
| 1772 | fn fieldAccessStruct( | |
| 1773 | s: *Sema, | |
| 1774 | lhs: Air.Inst.Ref, | |
| 1775 | lhs_type: Type, | |
| 1776 | struct_type: InternPool.Key.StructType, | |
| 1777 | field: Teg.Inst.Ref, | |
| 1778 | ) !Air.Inst.Ref { | |
| 1779 | const rhs = try s.getCoercedValue(s.lazyNode(.rhs), field, .string); | |
| 1780 | const string = rhs.asString(s.ip); | |
| 1781 | ||
| 1782 | const found_index: u32 = found_index: { | |
| 1783 | const lookup = string.tryToNullTerminatedString( | |
| 1784 | s.ip, | |
| 1785 | s.gpa, | |
| 1786 | ) catch |err| switch (err) { | |
| 1787 | error.HasNulls => break :found_index null, | |
| 1788 | else => |e| return e, | |
| 1789 | }; | |
| 1518 | 1790 | |
| 1519 | switch (op) { | |
| 1520 | .add => switch (left_ty) { | |
| 1521 | .numeric_literal, .float => { | |
| 1522 | const l = lhs.toFloat(s.ip) orelse | |
| 1523 | unreachable; // must be coerced | |
| 1524 | const r = rhs.toFloat(s.ip) orelse | |
| 1525 | unreachable; // must be coerced | |
| 1791 | break :found_index for (struct_type.field_keys, 0..) |key, i| { | |
| 1792 | if (key == lookup) | |
| 1793 | break @as(u32, @intCast(i)); | |
| 1794 | } else null; | |
| 1795 | } orelse { | |
| 1796 | return s.fail(.field_lookup_failed, .{ | |
| 1797 | .ty = lhs_type, | |
| 1798 | .src = s.lazyNode(.lhs), | |
| 1799 | .field = rhs, | |
| 1800 | }); | |
| 1801 | }; | |
| 1526 | 1802 | |
| 1527 | const result = l + r; | |
| 1803 | switch (lhs.decode()) { | |
| 1804 | .value => |value| { | |
| 1805 | _, const trail = value.valueData(s.ip, .struct_value).getTrail(s.ip); | |
| 1806 | return trail.at(&s.ip.extra, Value, found_index).toAir(); | |
| 1807 | }, | |
| 1808 | .index => |index| { | |
| 1809 | try s.ensureAtRuntime(s.lazyNode(.lhs)); | |
| 1810 | return s.addRef(.field_struct, .{ | |
| 1811 | .base = index, | |
| 1812 | .field_index = found_index, | |
| 1813 | }); | |
| 1814 | }, | |
| 1815 | } | |
| 1816 | } | |
| 1528 | 1817 | |
| 1529 | return try s.makeValue(if (left_ty == .numeric_literal) | |
| 1530 | .{ .comptime_float = result } | |
| 1531 | else | |
| 1532 | .{ .float = result }); | |
| 1533 | }, | |
| 1534 | .int32 => { | |
| 1535 | const l = lhs.toInt32(s.ip) orelse | |
| 1536 | unreachable; // must be coerced | |
| 1537 | const r = rhs.toInt32(s.ip) orelse | |
| 1538 | unreachable; // must be coerced | |
| 1818 | fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Teg.Inst.Ref) !Air.Inst.Ref { | |
| 1819 | const rhs = s.getRef(rhs_teg, .marked); | |
| 1539 | 1820 | |
| 1540 | const result = std.math.add(i32, l, r) catch | |
| 1541 | @panic("TODO: out of range"); | |
| 1821 | switch (s.typeOfRef(rhs)) { | |
| 1822 | .string => { | |
| 1823 | const value = try s.getValue(s.lazyNode(.rhs), rhs_teg); | |
| 1824 | const string = value.asString(s.ip).toSlice(s.ip); | |
| 1825 | if (mem.eql(u8, string, "len")) { | |
| 1826 | switch (lhs.decode()) { | |
| 1827 | .value => |val| { | |
| 1828 | return try s.makeValueAir(.{ | |
| 1829 | .int32 = @intCast(val.toKey(s.ip).array_value.items.len), | |
| 1830 | }); | |
| 1831 | }, | |
| 1832 | .index => { | |
| 1833 | try s.ensureAtRuntime(s.lazyNode(.main_node)); | |
| 1834 | return s.addRef(.array_len, .{ | |
| 1835 | .operand = lhs, | |
| 1836 | }); | |
| 1837 | }, | |
| 1838 | } | |
| 1839 | } | |
| 1542 | 1840 | |
| 1543 | return try s.makeValue(.{ .int32 = result }); | |
| 1544 | }, | |
| 1545 | .string => { | |
| 1546 | const l = lhs.asString(s.ip).toSlice(s.ip); | |
| 1547 | const r = rhs.asString(s.ip).toSlice(s.ip); | |
| 1548 | var sfa = std.heap.stackFallback(4096, s.arena); | |
| 1549 | const alloc = sfa.get(); | |
| 1550 | const joined = try std.mem.concat(alloc, u8, &.{ l, r }); | |
| 1551 | defer alloc.free(joined); | |
| 1552 | return try s.makeValue(.{ .string = joined }); | |
| 1553 | }, | |
| 1554 | else => unreachable, | |
| 1841 | return s.fail(.field_lookup_failed, .{ | |
| 1842 | .ty = lhs_type, | |
| 1843 | .src = s.lazyNode(.lhs), | |
| 1844 | .field = value, | |
| 1845 | }); | |
| 1555 | 1846 | }, |
| 1556 | inline .sub, .mul, .div => |tag| switch (left_ty) { | |
| 1557 | .numeric_literal, .float => { | |
| 1558 | const l = lhs.toFloat(s.ip) orelse | |
| 1559 | unreachable; // must be coerced | |
| 1560 | const r = rhs.toFloat(s.ip) orelse | |
| 1561 | unreachable; // must be coerced | |
| 1562 | ||
| 1563 | const result = switch (tag) { | |
| 1564 | .sub => l - r, | |
| 1565 | .mul => l * r, | |
| 1566 | .div => l / r, | |
| 1567 | else => @compileError(unreachable), | |
| 1568 | }; | |
| 1569 | ||
| 1570 | return try s.makeValue(if (left_ty == .numeric_literal) | |
| 1571 | .{ .comptime_float = result } | |
| 1572 | else | |
| 1573 | .{ .float = result }); | |
| 1574 | }, | |
| 1575 | .int32 => { | |
| 1576 | const l = lhs.toInt32(s.ip) orelse | |
| 1577 | unreachable; // must be coerced | |
| 1578 | const r = rhs.toInt32(s.ip) orelse | |
| 1579 | unreachable; // must be coerced | |
| 1847 | else => { | |
| 1848 | const index = try s.getCoercedRef(s.lazyNode(.rhs), rhs_teg, .int32, .marked); | |
| 1580 | 1849 | |
| 1581 | const result = switch (tag) { | |
| 1582 | .sub => std.math.sub(i32, l, r), | |
| 1583 | .mul => std.math.mul(i32, l, r), | |
| 1584 | .div => return try s.makeValue(.{ .float = @as(f64, @floatFromInt(l)) / @as(f64, @floatFromInt(r)) }), | |
| 1585 | else => @compileError(unreachable), | |
| 1586 | } catch @panic("TODO: out of range"); | |
| 1850 | if (lhs.kind == .value and index.kind == .value) { | |
| 1851 | // TODO: | |
| 1852 | } | |
| 1587 | 1853 | |
| 1588 | return try s.makeValue(.{ .int32 = result }); | |
| 1589 | }, | |
| 1590 | else => unreachable, | |
| 1591 | }, | |
| 1592 | inline .lt, .gt => |tag| { | |
| 1593 | const l = lhs.toFloat(s.ip) orelse | |
| 1594 | unreachable; // must be coerced | |
| 1595 | const r = rhs.toFloat(s.ip) orelse | |
| 1596 | unreachable; // must be coerced | |
| 1854 | try s.ensureAtRuntime(s.lazyNode(.main_node)); | |
| 1597 | 1855 | |
| 1598 | const result = switch (tag) { | |
| 1599 | .lt => l < r, | |
| 1600 | .gt => l > r, | |
| 1601 | else => @compileError(unreachable), | |
| 1602 | }; | |
| 1603 | return @as( | |
| 1604 | InternPool.Value, | |
| 1605 | if (result) .bool_true else .bool_false, | |
| 1606 | ); | |
| 1607 | }, | |
| 1608 | .eq => { | |
| 1609 | const result = lhs == rhs; | |
| 1610 | return @as( | |
| 1611 | InternPool.Value, | |
| 1612 | if (result) .bool_true else .bool_false, | |
| 1613 | ); | |
| 1856 | return s.addRef(.field_array, .{ | |
| 1857 | .lhs = lhs, | |
| 1858 | .rhs = index, | |
| 1859 | }); | |
| 1614 | 1860 | }, |
| 1615 | 1861 | } |
| 1616 | 1862 | } |
| 1617 | 1863 | |
| 1618 | const IncompleteBlock = struct { | |
| 1619 | block_index: Air.Inst.Index, | |
| 1864 | // | |
| 1620 | 1865 | |
| 1621 | pub fn finish(block: IncompleteBlock, s: *Sema, scratch_mark: core.U32Stack.Mark) !Air.Inst.Index { | |
| 1622 | s.setInstData(.block, block.block_index, try s.scratchMarkToIndexList(scratch_mark)); | |
| 1623 | return block.block_index; | |
| 1624 | } | |
| 1625 | }; | |
| 1866 | fn tempAir(s: *const Sema) Air { | |
| 1867 | return .{ | |
| 1868 | .insts = s.func.?.instructions.slice(), | |
| 1869 | .extra = s.func.?.extra.constant(), | |
| 1870 | }; | |
| 1871 | } | |
| 1626 | 1872 | |
| 1627 | fn addBlock(s: *Sema) !IncompleteBlock { | |
| 1628 | try s.air_instructions.append(s.gpa, .{ | |
| 1629 | .tag = .block, | |
| 1630 | .data = undefined, | |
| 1631 | .flags = .{}, | |
| 1632 | }); | |
| 1633 | return .{ .block_index = @enumFromInt(s.air_instructions.len - 1) }; | |
| 1873 | fn typeOfRef(s: *Sema, ref: Air.Inst.Ref) Type { | |
| 1874 | return switch (ref.decode()) { | |
| 1875 | .value => |v| v.typeOf(s.ip), | |
| 1876 | .index => |i| s.tempAir().typeOfIndex(i, s.ip), | |
| 1877 | }; | |
| 1634 | 1878 | } |
| 1635 | 1879 | |
| 1636 | fn addInstRef(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref { | |
| 1637 | try s.air_instructions.append(s.gpa, .{ | |
| 1880 | fn pushFrame(s: *Sema) !void { | |
| 1881 | (try s.frame_stack.addOne(s.arena())).* = s.frame; | |
| 1882 | s.frame = undefined; | |
| 1883 | } | |
| 1884 | ||
| 1885 | fn popFrame(s: *Sema) void { | |
| 1886 | s.frame = s.frame_stack.pop(); | |
| 1887 | } | |
| 1888 | ||
| 1889 | fn addIndexUntracked(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Index { | |
| 1890 | assert(s.frame.mode == .run_time); | |
| 1891 | try s.func.?.instructions.append(s.gpa, .{ | |
| 1638 | 1892 | .tag = tag, |
| 1639 | .data = Air.EncodedData.encode(tag, data), | |
| 1893 | .data = .encode(tag, data), | |
| 1640 | 1894 | .flags = .{}, |
| 1641 | 1895 | }); |
| 1642 | const index: Air.Inst.Index = @enumFromInt(s.air_instructions.len - 1); | |
| 1643 | try s.scratch.add(s.arena, Air.Inst.Index, index); | |
| 1644 | return index.toRef(); | |
| 1896 | return @enumFromInt(s.func.?.instructions.len - 1); | |
| 1645 | 1897 | } |
| 1646 | 1898 | |
| 1647 | fn setInstData( | |
| 1648 | s: *Sema, | |
| 1649 | comptime tag: Air.Inst.Tag, | |
| 1650 | index: Air.Inst.Index, | |
| 1651 | data: tag.Data(), | |
| 1652 | ) void { | |
| 1653 | const i = @intFromEnum(index); | |
| 1654 | assert(s.air_instructions.items(.tag)[i] == tag); | |
| 1655 | s.air_instructions.items(.data)[i] = Air.EncodedData.encode(tag, data); | |
| 1899 | fn addIndex(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Index { | |
| 1900 | const index = try s.addIndexUntracked(tag, data); | |
| 1901 | try s.scratch.add(s.arena(), Air.Inst.Index, index); | |
| 1902 | return index; | |
| 1903 | } | |
| 1904 | ||
| 1905 | fn addRef(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref { | |
| 1906 | return (try s.addIndex(tag, data)).toRef(); | |
| 1907 | } | |
| 1908 | ||
| 1909 | fn typeOfIndex(s: *Sema, i: Air.Inst.Index) Type { | |
| 1910 | return s.tempAir().typeOfIndex(i, s.ip); | |
| 1911 | } | |
| 1912 | ||
| 1913 | fn namespace(s: *const Sema) *Namespace { | |
| 1914 | return s.ip.namespacePtr(s.ns_index); | |
| 1656 | 1915 | } |
| 1657 | 1916 | |
| 1658 | 1917 | fn makeValue(s: *Sema, key: InternPool.Key) !Value { |
| ... | ... | @@ -1667,91 +1926,85 @@ fn makeValueAir(s: *Sema, key: InternPool.Key) !Air.Inst.Ref { |
| 1667 | 1926 | return (try s.makeValue(key)).toAir(); |
| 1668 | 1927 | } |
| 1669 | 1928 | |
| 1670 | fn mapTeg(s: *Sema, inst: Teg.Inst.Ref) Air.Inst.Ref { | |
| 1671 | return switch (inst.decode()) { | |
| 1672 | .value => |value| value.toAir(), | |
| 1673 | .index => |index| s.inst_map.get(index) orelse { | |
| 1674 | if (std.debug.runtime_safety) { | |
| 1675 | std.debug.panic("Unmapped operand %{d}", .{@intFromEnum(index)}); | |
| 1676 | } | |
| 1677 | unreachable; | |
| 1678 | }, | |
| 1679 | }; | |
| 1929 | fn lazyNode(s: *const Sema, op: Report.Operand) LazyNode { | |
| 1930 | return s.frame.currentIndex().op(op).lazyNode(s.techDebtDeclIndex()); | |
| 1680 | 1931 | } |
| 1681 | 1932 | |
| 1682 | fn scratchMarkToIndexList(s: *Sema, mark: core.U32Stack.Mark) !Air.IndexList { | |
| 1683 | const items = mark.slice(u32); | |
| 1684 | const extra_top = s.air_extra.list.items.len; | |
| 1685 | try s.air_extra.list.appendSlice(s.gpa, items); | |
| 1686 | return .{ | |
| 1687 | .start = @intCast(extra_top), | |
| 1688 | .len = @intCast(items.len), | |
| 1933 | /// Using this function is incorrect because as of "Sema v5" rewrite, | |
| 1934 | /// it is incorrect to use a Decl.Index as the source of truth. | |
| 1935 | fn techDebtDeclIndex(s: *const Sema) Decl.Index { | |
| 1936 | return switch (s.owner.unwrap()) { | |
| 1937 | .decl => |decl| decl, | |
| 1938 | .func => |func| s.ip.valueToKey(func).func.owner_decl, | |
| 1689 | 1939 | }; |
| 1690 | 1940 | } |
| 1691 | 1941 | |
| 1692 | fn tempAir(s: *const Sema) Air { | |
| 1693 | return .{ | |
| 1694 | .insts = s.air_instructions.slice(), | |
| 1695 | .extra = s.air_extra.constant(), | |
| 1696 | }; | |
| 1942 | fn addWarn(s: *Sema, comptime kind: Report.Err.FieldEnum, data: core.FieldType(Report.Err, kind)) !void { | |
| 1943 | try s.report.warn(kind, data); | |
| 1697 | 1944 | } |
| 1698 | 1945 | |
| 1699 | fn typeOfRef(s: *Sema, ref: Air.Inst.Ref) InternPool.Type { | |
| 1700 | return s.tempAir().typeOf(ref, s.ip); | |
| 1946 | fn addError(s: *Sema, comptime kind: Report.Err.FieldEnum, data: core.FieldType(Report.Err, kind)) !void { | |
| 1947 | try s.report.err(kind, data); | |
| 1701 | 1948 | } |
| 1702 | 1949 | |
| 1703 | fn lookupDecl(s: *Sema, name: NullTerminatedString) ?InternPool.DeclIndex { | |
| 1704 | var nsi = s.namespace_index; | |
| 1705 | while (true) { | |
| 1706 | const ns = s.ip.namespacePtr(nsi); | |
| 1707 | if (ns.lookupDirect(s.comp, name)) |decl_id| { | |
| 1708 | return decl_id; | |
| 1709 | } | |
| 1710 | nsi = ns.parent.unwrap() orelse | |
| 1711 | unreachable; // transform ensures all identifiers get resolved | |
| 1712 | } | |
| 1950 | fn fail(s: *Sema, comptime kind: Report.Err.FieldEnum, data: core.FieldType(Report.Err, kind)) Error { | |
| 1951 | try s.report.err(kind, data); | |
| 1952 | return Error.AnalysisFail; | |
| 1713 | 1953 | } |
| 1714 | 1954 | |
| 1715 | fn nodeLoc(s: *const Sema, in: Teg.Inst.Index, op: Report.Operand) Report.LazyNode { | |
| 1716 | return s.lazyNode(in.op(op)); | |
| 1955 | fn coercionError(s: *Sema, src: LazyNode, from: Type, to: Type) Error { | |
| 1956 | return s.fail(.coercion, .{ | |
| 1957 | .from = from, | |
| 1958 | .to = to, | |
| 1959 | .src = src, | |
| 1960 | }); | |
| 1717 | 1961 | } |
| 1718 | 1962 | |
| 1719 | fn addWarn(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) !void { | |
| 1720 | try s.report.warnAt(kind, data, @returnAddress()); | |
| 1963 | fn assertHasFunction(s: *const Sema) void { | |
| 1964 | assert(s.func_stack_len > 0); | |
| 1721 | 1965 | } |
| 1722 | 1966 | |
| 1723 | fn addError(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) !void { | |
| 1724 | try s.report.errAt(kind, data, @returnAddress()); | |
| 1967 | /// TODO: This function is designed incorrectly. it should be | |
| 1968 | /// - "cannot be computed at comptime due to runtime operand" | |
| 1969 | /// - <exact operation> cannot be performed at compile time | |
| 1970 | fn ensureAtRuntime(s: *Sema, src: LazyNode) !void { | |
| 1971 | if (s.frame.mode == .compile_time) { | |
| 1972 | return s.fail(.cannot_be_comptime, .{ .src = src }); | |
| 1973 | } | |
| 1725 | 1974 | } |
| 1726 | 1975 | |
| 1727 | fn fail(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) Error { | |
| 1728 | try s.report.errAt(kind, data, @returnAddress()); | |
| 1729 | return Error.AnalysisFail; | |
| 1976 | fn arena(s: *Sema) Allocator { | |
| 1977 | return s.arena_state.allocator(); | |
| 1730 | 1978 | } |
| 1731 | 1979 | |
| 1732 | fn coercionError(s: *Sema, src: InstOp, from: Type, to: Type) Error { | |
| 1733 | return s.fail(.coercion, .{ | |
| 1734 | .from = from, | |
| 1735 | .to = to, | |
| 1736 | .src = s.lazyNode(src), | |
| 1737 | }); | |
| 1980 | fn debug(s: *Sema, comptime fmt: []const u8, args: anytype) void { | |
| 1981 | if (s.comp.options.verbose_sema) | |
| 1982 | s.comp.out.print( | |
| 1983 | "sema {}: " ++ fmt ++ "\n", | |
| 1984 | .{s.owner.dump(s.ip)} ++ args, | |
| 1985 | ) catch {}; | |
| 1738 | 1986 | } |
| 1739 | 1987 | |
| 1740 | fn lazyNode(s: *const Sema, src: InstOp) Report.LazyNode { | |
| 1741 | return src.lazyNode(s.decl_index); | |
| 1742 | } | |
| 1988 | /// Frame uses @fieldParentPtr to do some pointer arithmetic. To ensure every | |
| 1989 | /// pointer to `Sema` is valid, it has a field set to a specific 128-bit number. | |
| 1990 | /// Failing assert(s.magic == .valid) means you have an invalid pointer. | |
| 1991 | const Magic = if (runtime_safety) enum(u128) { | |
| 1992 | valid = 0x57453ef77efd3a72c7a0ebe03f80878c, | |
| 1993 | _, | |
| 1994 | } else enum { valid }; | |
| 1743 | 1995 | |
| 1744 | 1996 | const Sema = @This(); |
| 1745 | 1997 | |
| 1746 | 1998 | const std = @import("std"); |
| 1747 | 1999 | const mem = std.mem; |
| 1748 | 2000 | const math = std.math; |
| 1749 | const Allocator = std.mem.Allocator; | |
| 1750 | 2001 | const assert = std.debug.assert; |
| 1751 | const log = std.log.scoped(.sema); | |
| 2002 | const Allocator = std.mem.Allocator; | |
| 1752 | 2003 | const DynamicBitSetUnmanaged = std.DynamicBitSetUnmanaged; |
| 2004 | const DynamicArray = std.ArrayListUnmanaged; | |
| 1753 | 2005 | |
| 1754 | 2006 | const core = @import("mod.zig"); |
| 2007 | const runtime_safety = core.runtime_safety; | |
| 1755 | 2008 | const assertFailed = core.assertFailed; |
| 1756 | 2009 | const StoredTrace = core.StoredTrace; |
| 1757 | 2010 | |
| ... | ... | @@ -1761,18 +2014,18 @@ const Decl = Comp.Decl; |
| 1761 | 2014 | const Namespace = Comp.Namespace; |
| 1762 | 2015 | |
| 1763 | 2016 | const Teg = core.Teg; |
| 1764 | const InstOp = Teg.InstOp; | |
| 1765 | 2017 | |
| 1766 | 2018 | const Air = core.Air; |
| 1767 | 2019 | const Ast = core.Ast; |
| 1768 | 2020 | const store = core.array; |
| 1769 | 2021 | |
| 1770 | 2022 | const Report = core.Report; |
| 1771 | const operandArgument = Report.Operand.argument; | |
| 2023 | const LazyNode = Report.LazyNode; | |
| 1772 | 2024 | |
| 1773 | 2025 | const InternPool = core.InternPool; |
| 1774 | 2026 | const Value = InternPool.Value; |
| 1775 | 2027 | const Type = InternPool.Type; |
| 2028 | const AnalysisUnit = InternPool.AnalysisUnit; | |
| 1776 | 2029 | const NullTerminatedString = InternPool.NullTerminatedString; |
| 1777 | 2030 | |
| 1778 | 2031 | const tracer = @import("tracer"); |
src/core/StoredTrace.zig+2| ... | ... | @@ -41,6 +41,8 @@ pub fn from(stack_trace: ?*std.builtin.StackTrace) StoredTrace { |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | pub const BlankImpl = struct { |
| 44 | pub const empty: BlankImpl = .{}; | |
| 45 | ||
| 44 | 46 | pub fn trace(_: *BlankImpl) std.builtin.StackTrace { |
| 45 | 47 | @compileError("trace is not available with StoredTrace.BlankImpl"); |
| 46 | 48 | } |
src/core/Teg.Dump.zig+100-28| ... | ... | @@ -46,39 +46,57 @@ const State = struct { |
| 46 | 46 | .module => { |
| 47 | 47 | try s.dumpInstList(writer, teg.moduleDeclarations(inst), '{', '}'); |
| 48 | 48 | }, |
| 49 | .declaration => { | |
| 49 | .declaration, .variable_declaration, .test_declaration => { | |
| 50 | 50 | const decl = teg.fullDecl(inst); |
| 51 | try writer.print(" ({s}) ", .{@tagName(decl.data)}); | |
| 52 | 51 | |
| 52 | try writer.writeAll(" "); | |
| 53 | 53 | const name = decl.name.slice(teg.string_bytes); |
| 54 | 54 | if (name.len == 0) { |
| 55 | 55 | try tty.setColor(writer, .bold); |
| 56 | try writer.writeAll("<top level code>"); | |
| 56 | try writer.writeAll("[no name]"); | |
| 57 | 57 | } else { |
| 58 | 58 | try tty.setColor(writer, .magenta); |
| 59 | 59 | try writer.writeAll(name); |
| 60 | 60 | } |
| 61 | 61 | try tty.setColor(writer, .reset); |
| 62 | ||
| 62 | try writer.writeAll(" ("); | |
| 63 | try writer.print("{d}", .{decl.instruction_count}); | |
| 64 | try writer.writeAll(")"); | |
| 65 | try tty.setColor(writer, .bright_black); | |
| 66 | try writer.writeAll(" ["); | |
| 67 | try writer.writeAll(&std.fmt.bytesToHex(decl.hash, .lower)); | |
| 68 | try writer.writeAll("]"); | |
| 69 | try tty.setColor(writer, .reset); | |
| 70 | try writer.writeAll(" ("); | |
| 71 | s.indent += 1; | |
| 72 | try s.newline(writer); | |
| 73 | try s.dumpRef(writer, decl.body); | |
| 74 | s.indent -= 1; | |
| 75 | try s.newline(writer); | |
| 76 | try writer.writeAll(")"); | |
| 77 | }, | |
| 78 | .func => { | |
| 79 | const func: Teg.Func = teg.instPayload(.func, inst); | |
| 63 | 80 | try writer.writeAll(" {"); |
| 64 | 81 | s.indent += 1; |
| 65 | 82 | try s.newline(writer); |
| 66 | ||
| 67 | switch (decl.data) { | |
| 68 | .function => |fn_data| { | |
| 69 | try writer.writeAll("params: "); | |
| 70 | try s.dumpRefList(writer, fn_data.params.slice(teg), '[', ']'); | |
| 71 | try s.newline(writer); | |
| 72 | if (fn_data.return_type.unwrap()) |return_type| { | |
| 73 | try writer.writeAll("return_type: "); | |
| 74 | try s.dumpRef(writer, return_type); | |
| 75 | try s.newline(writer); | |
| 76 | } | |
| 77 | }, | |
| 78 | .immutable => {}, | |
| 83 | try writer.writeAll("params: "); | |
| 84 | try s.dumpOptionalRefList(writer, func.params.slice(teg), '[', ']'); | |
| 85 | try s.newline(writer); | |
| 86 | try writer.writeAll("return_type: "); | |
| 87 | if (func.return_type.unwrap()) |ret| | |
| 88 | try s.dumpRef(writer, ret) | |
| 89 | else { | |
| 90 | try tty.setColor(writer, .bright_black); | |
| 91 | try writer.writeAll("<inferred>"); | |
| 92 | try tty.setColor(writer, .reset); | |
| 79 | 93 | } |
| 94 | try s.newline(writer); | |
| 95 | try writer.writeAll("captures: "); | |
| 96 | try s.dumpInstList2(writer, func.captures.slice(teg), '[', ']'); | |
| 97 | try s.newline(writer); | |
| 80 | 98 | try writer.writeAll("body: "); |
| 81 | try s.dumpRef(writer, decl.body); | |
| 99 | try s.dumpInst(writer, func.body); | |
| 82 | 100 | s.indent -= 1; |
| 83 | 101 | try s.newline(writer); |
| 84 | 102 | try writer.writeAll("}"); |
| ... | ... | @@ -91,8 +109,8 @@ const State = struct { |
| 91 | 109 | try tty.setColor(writer, .reset); |
| 92 | 110 | try writer.writeAll(")"); |
| 93 | 111 | }, |
| 94 | .block => { | |
| 95 | const block = teg.instData(.block, inst); | |
| 112 | inline .block, .@"comptime" => |t| { | |
| 113 | const block = teg.instData(t, inst); | |
| 96 | 114 | const index_list = block.extra_index.get(teg); |
| 97 | 115 | try s.dumpInstList(writer, index_list.slice(teg), '[', ']'); |
| 98 | 116 | }, |
| ... | ... | @@ -156,6 +174,26 @@ const State = struct { |
| 156 | 174 | try s.dumpRefMarker(writer, data.operand); |
| 157 | 175 | try writer.writeAll(")"); |
| 158 | 176 | }, |
| 177 | .store_field => { | |
| 178 | const data: Teg.StoreField = teg.instData(.store_field, inst).extra_index.get(teg); | |
| 179 | try writer.writeAll("("); | |
| 180 | try s.dumpRefMarker(writer, data.base); | |
| 181 | try writer.writeAll(", "); | |
| 182 | try s.dumpRefMarker(writer, data.field); | |
| 183 | try writer.writeAll(", "); | |
| 184 | try s.dumpRefMarker(writer, data.operand); | |
| 185 | try writer.writeAll(")"); | |
| 186 | }, | |
| 187 | .store_decl => { | |
| 188 | const data: Teg.StoreVarDecl = teg.instData(.store_decl, inst).extra_index.get(teg); | |
| 189 | try writer.writeAll("("); | |
| 190 | try tty.setColor(writer, .magenta); | |
| 191 | try writer.writeAll(data.var_decl.slice(teg.string_bytes)); | |
| 192 | try tty.setColor(writer, .reset); | |
| 193 | try writer.writeAll(", "); | |
| 194 | try s.dumpRefMarker(writer, data.operand); | |
| 195 | try writer.writeAll(")"); | |
| 196 | }, | |
| 159 | 197 | .string => { |
| 160 | 198 | const data: Teg.SlicedString = teg.instData(.string, inst).extra_index.get(teg); |
| 161 | 199 | try tty.setColor(writer, .green); |
| ... | ... | @@ -170,12 +208,13 @@ const State = struct { |
| 170 | 208 | .negate, |
| 171 | 209 | .bool_not, |
| 172 | 210 | .bit_not, |
| 173 | .js, | |
| 211 | .@"extern", | |
| 174 | 212 | .import, |
| 175 | 213 | .fetch, |
| 176 | 214 | .compile_error, |
| 177 | 215 | .type_of, |
| 178 | 216 | .array_type, |
| 217 | .assert, | |
| 179 | 218 | => |comptime_tag| { |
| 180 | 219 | const data = teg.instData(comptime_tag, inst); |
| 181 | 220 | try writer.writeAll("("); |
| ... | ... | @@ -221,12 +260,6 @@ const State = struct { |
| 221 | 260 | try s.newline(writer); |
| 222 | 261 | try writer.writeAll("}"); |
| 223 | 262 | }, |
| 224 | .@"comptime" => { | |
| 225 | const data: Teg.RefNode = teg.instData(.@"comptime", inst); | |
| 226 | try writer.writeAll("("); | |
| 227 | try s.dumpRef(writer, data.operand); | |
| 228 | try writer.writeAll(")"); | |
| 229 | }, | |
| 230 | 263 | .struct_decl => { |
| 231 | 264 | const data: Teg.StructDecl = teg.instPayload(.struct_decl, inst); |
| 232 | 265 | try writer.writeAll(" {"); |
| ... | ... | @@ -274,7 +307,7 @@ const State = struct { |
| 274 | 307 | |
| 275 | 308 | for (data.list.slice(teg)) |item| { |
| 276 | 309 | try s.newline(writer); |
| 277 | try s.dumpRef(writer, item); | |
| 310 | try s.dumpRefMarker(writer, item); | |
| 278 | 311 | try writer.writeAll(","); |
| 279 | 312 | } |
| 280 | 313 | |
| ... | ... | @@ -362,6 +395,22 @@ const State = struct { |
| 362 | 395 | try writer.print("{c}", .{r}); |
| 363 | 396 | } |
| 364 | 397 | |
| 398 | fn dumpInstList2(state: *State, writer: anytype, list: []const Teg.Inst.Index, l: u8, r: u8) !void { | |
| 399 | try writer.print(" {c}", .{l}); | |
| 400 | if (list.len > 0) { | |
| 401 | state.indent += 1; | |
| 402 | ||
| 403 | for (list) |inst| { | |
| 404 | try state.newline(writer); | |
| 405 | try state.dumpRefMarker(writer, inst.toRef()); | |
| 406 | } | |
| 407 | ||
| 408 | state.indent -= 1; | |
| 409 | try state.newline(writer); | |
| 410 | } | |
| 411 | try writer.print("{c}", .{r}); | |
| 412 | } | |
| 413 | ||
| 365 | 414 | fn dumpRefList(state: *State, writer: anytype, list: []const Teg.Inst.Ref, l: u8, r: u8) !void { |
| 366 | 415 | try writer.print(" {c}", .{l}); |
| 367 | 416 | if (list.len > 0) { |
| ... | ... | @@ -379,6 +428,29 @@ const State = struct { |
| 379 | 428 | try writer.print("{c}", .{r}); |
| 380 | 429 | } |
| 381 | 430 | |
| 431 | fn dumpOptionalRefList(state: *State, writer: anytype, list: []const Teg.Inst.Ref.Optional, l: u8, r: u8) !void { | |
| 432 | try writer.print(" {c}", .{l}); | |
| 433 | if (list.len > 0) { | |
| 434 | state.indent += 1; | |
| 435 | ||
| 436 | for (list) |optional_ref| { | |
| 437 | try state.newline(writer); | |
| 438 | if (optional_ref.unwrap()) |ref| | |
| 439 | try state.dumpRef(writer, ref) | |
| 440 | else { | |
| 441 | try state.tty.setColor(writer, .bright_black); | |
| 442 | try writer.writeAll("<inferred>"); | |
| 443 | try state.tty.setColor(writer, .reset); | |
| 444 | } | |
| 445 | } | |
| 446 | ||
| 447 | state.indent -= 1; | |
| 448 | ||
| 449 | try state.newline(writer); | |
| 450 | } | |
| 451 | try writer.print("{c}", .{r}); | |
| 452 | } | |
| 453 | ||
| 382 | 454 | fn dumpRefMarkerList(state: *State, writer: anytype, list: []const Teg.Inst.Ref, l: u8, r: u8) !void { |
| 383 | 455 | try writer.print(" {c}", .{l}); |
| 384 | 456 | if (list.len > 0) { |
src/core/Teg.zig+381-68| ... | ... | @@ -10,12 +10,16 @@ pub const EncodedData = dod.EncodedData(Tag, u64); |
| 10 | 10 | pub const ExtraIndex = ExtraStore.Index; |
| 11 | 11 | pub const Dump = @import("Teg.Dump.zig"); |
| 12 | 12 | |
| 13 | pub const Tag = enum { | |
| 13 | pub const Tag = enum(u8) { | |
| 14 | 14 | /// Either the top level module or a `module` keyword. Contains a list of `.declarations` |
| 15 | 15 | /// Every analyzed module gets a `Comp.Namespace` |
| 16 | 16 | module, |
| 17 | 17 | /// Smallest unit of re-compilation. Every analyzed declaration gets a `Comp.Decl` |
| 18 | 18 | declaration, |
| 19 | /// Top-level memory location | |
| 20 | variable_declaration, | |
| 21 | /// Test declaration | |
| 22 | test_declaration, | |
| 19 | 23 | /// Retrieves a declaration value via it's name. Since declarations are |
| 20 | 24 | /// analyzed in isolation of each other, all references to instructions |
| 21 | 25 | /// outside of the current declaration must be in the form of this. |
| ... | ... | @@ -30,6 +34,10 @@ pub const Tag = enum { |
| 30 | 34 | memory, |
| 31 | 35 | /// Re-assign a memory cell with a new ref. |
| 32 | 36 | store, |
| 37 | /// Re-assign a global variable decl with a new ref. | |
| 38 | store_decl, | |
| 39 | /// Re-assign a field with a new ref | |
| 40 | store_field, | |
| 33 | 41 | /// Performs a type coercion, such as in variable declarations. |
| 34 | 42 | /// The resulting Air Ref will be guaranteed to be the coerced type. |
| 35 | 43 | coercion, |
| ... | ... | @@ -39,12 +47,14 @@ pub const Tag = enum { |
| 39 | 47 | discard, |
| 40 | 48 | /// Asserts the operand is `void`. Used for undiscarded expressions. |
| 41 | 49 | ensure_void, |
| 42 | /// Numeric constant. | |
| 43 | number, | |
| 44 | /// String literal constant. | |
| 45 | string, | |
| 46 | /// Branch takes a boolean condition, and yes and no paths. | |
| 50 | ||
| 51 | /// Branch takes a boolean condition and takes either a yes or no path. | |
| 47 | 52 | @"if", |
| 53 | /// Given a block instruction | |
| 54 | @"comptime", | |
| 55 | ||
| 56 | /// A function expression, with or without captures | |
| 57 | func, | |
| 48 | 58 | |
| 49 | 59 | // The following instructions are used to implement `Transform.ResultType` |
| 50 | 60 | result_type_argument, |
| ... | ... | @@ -52,10 +62,17 @@ pub const Tag = enum { |
| 52 | 62 | result_type_tuple_field, |
| 53 | 63 | result_type_array_value, |
| 54 | 64 | |
| 55 | struct_decl, | |
| 56 | struct_init, | |
| 65 | /// Numeric constant. | |
| 66 | number, | |
| 67 | /// String literal constant. | |
| 68 | string, | |
| 57 | 69 | |
| 70 | /// Constructs a unique struct type. | |
| 71 | struct_decl, | |
| 72 | /// Constructs an array type | |
| 58 | 73 | array_type, |
| 74 | ||
| 75 | struct_init, | |
| 59 | 76 | array_init, |
| 60 | 77 | |
| 61 | 78 | add, |
| ... | ... | @@ -81,13 +98,14 @@ pub const Tag = enum { |
| 81 | 98 | negate, |
| 82 | 99 | bool_not, |
| 83 | 100 | bit_not, |
| 101 | assert, | |
| 84 | 102 | field, |
| 85 | 103 | index, |
| 86 | 104 | call, |
| 87 | @"comptime", | |
| 105 | type_of, | |
| 106 | /// Implements the `@extern` builtin | |
| 107 | @"extern", | |
| 88 | 108 | |
| 89 | /// Implements the `@js` builtin | |
| 90 | js, | |
| 91 | 109 | /// Implements the `@jsNew` builtin |
| 92 | 110 | js_new, |
| 93 | 111 | /// Implements the `@import` builtin |
| ... | ... | @@ -96,27 +114,32 @@ pub const Tag = enum { |
| 96 | 114 | fetch, |
| 97 | 115 | /// Implements the `@compileError` builtin |
| 98 | 116 | compile_error, |
| 99 | /// Implements the `@typeOf` builtin | |
| 100 | type_of, | |
| 101 | 117 | |
| 102 | 118 | pub fn Data(comptime tag: Tag) type { |
| 103 | 119 | return switch (tag) { |
| 104 | .module => PlNode(IndexList), | |
| 105 | .declaration => PlNode(Decl), | |
| 106 | .block => PlNode(IndexList), | |
| 120 | .module, .block, .@"comptime" => PlNode(IndexList), | |
| 121 | ||
| 122 | .declaration, | |
| 123 | .variable_declaration, | |
| 124 | .test_declaration, | |
| 125 | => PlNode(Decl), | |
| 107 | 126 | .coercion => PlNode(Coercion), |
| 108 | 127 | .@"break" => PlNode(Break), |
| 109 | 128 | .number => PlNode(Float), |
| 110 | 129 | .string => PlNode(SlicedString), |
| 111 | 130 | .call => PlNode(Call), |
| 112 | 131 | .@"if" => PlNode(If), |
| 113 | .load_decl => StrNode, | |
| 114 | .param => DataNode, | |
| 115 | 132 | .store => PlNode(Store), |
| 133 | .store_decl => PlNode(StoreVarDecl), | |
| 134 | .store_field => PlNode(StoreField), | |
| 116 | 135 | .js_new => PlNode(JsNew), |
| 117 | 136 | .struct_decl => PlNode(StructDecl), |
| 118 | 137 | .struct_init => PlNode(StructInit), |
| 119 | 138 | .array_init => PlNode(ArrayInit), |
| 139 | .func => PlNode(Func), | |
| 140 | ||
| 141 | .load_decl => StrNode, | |
| 142 | .param => DataNode, | |
| 120 | 143 | |
| 121 | 144 | .result_type_argument => ResultTypeArgument, |
| 122 | 145 | .result_type_struct_field => ResultTypeStructField, |
| ... | ... | @@ -130,13 +153,13 @@ pub const Tag = enum { |
| 130 | 153 | .negate, |
| 131 | 154 | .bool_not, |
| 132 | 155 | .bit_not, |
| 133 | .js, | |
| 156 | .@"extern", | |
| 134 | 157 | .import, |
| 135 | 158 | .fetch, |
| 136 | 159 | .compile_error, |
| 137 | .@"comptime", | |
| 138 | 160 | .type_of, |
| 139 | 161 | .array_type, |
| 162 | .assert, | |
| 140 | 163 | => RefNode, |
| 141 | 164 | |
| 142 | 165 | // Two instruction operands |
| ... | ... | @@ -241,11 +264,11 @@ pub const Inst = struct { |
| 241 | 264 | int32_type, |
| 242 | 265 | float_type, |
| 243 | 266 | string_type, |
| 244 | noreturn_type, | |
| 267 | never_type, | |
| 245 | 268 | type_type, |
| 246 | 269 | numeric_literal_type, |
| 247 | 270 | numeric_literal_array_type, |
| 248 | noreturn_array_type, | |
| 271 | never_array_type, | |
| 249 | 272 | |
| 250 | 273 | void, |
| 251 | 274 | bool_true, |
| ... | ... | @@ -315,6 +338,17 @@ pub const Store = struct { |
| 315 | 338 | operand: Inst.Ref, |
| 316 | 339 | }; |
| 317 | 340 | |
| 341 | pub const StoreVarDecl = struct { | |
| 342 | var_decl: NullTerminatedString, | |
| 343 | operand: Inst.Ref, | |
| 344 | }; | |
| 345 | ||
| 346 | pub const StoreField = struct { | |
| 347 | base: Inst.Ref, | |
| 348 | field: Inst.Ref, | |
| 349 | operand: Inst.Ref, | |
| 350 | }; | |
| 351 | ||
| 318 | 352 | pub const Coercion = struct { |
| 319 | 353 | type: Inst.Ref, |
| 320 | 354 | operand: Inst.Ref, |
| ... | ... | @@ -389,25 +423,19 @@ pub const ArrayInit = struct { |
| 389 | 423 | list: RefList, |
| 390 | 424 | }; |
| 391 | 425 | |
| 392 | /// This will contain trailing data depending on the .kind | |
| 393 | 426 | pub const Decl = struct { |
| 394 | 427 | name: NullTerminatedString, |
| 395 | 428 | hash: Cache.BinDigest, |
| 396 | kind: Kind, | |
| 397 | 429 | instruction_count: u32, |
| 398 | 430 | body: Inst.Ref, |
| 399 | ||
| 400 | pub const Kind = enum { | |
| 401 | // TODO: remove function, see #45 | |
| 402 | function, | |
| 403 | immutable, | |
| 404 | }; | |
| 405 | 431 | }; |
| 406 | 432 | |
| 407 | /// Trailing data for function declarations | |
| 408 | pub const DeclFn = struct { | |
| 409 | params: RefList, | |
| 433 | pub const Func = struct { | |
| 434 | params: OptionalRefList, | |
| 410 | 435 | return_type: Inst.Ref.Optional, |
| 436 | body: Inst.Index, | |
| 437 | captures: IndexList, | |
| 438 | instruction_count: u32, | |
| 411 | 439 | }; |
| 412 | 440 | |
| 413 | 441 | pub const ResultTypeArgument = struct { |
| ... | ... | @@ -440,6 +468,7 @@ pub const ResultTypeArrayValue = struct { |
| 440 | 468 | // TODO: make this an enum |
| 441 | 469 | const NodeOffset = i32; |
| 442 | 470 | |
| 471 | // TODO: remove InstOp. LazyNode should not have a decl | |
| 443 | 472 | pub const InstOp = struct { |
| 444 | 473 | index: Inst.Index, |
| 445 | 474 | op: Report.Operand, |
| ... | ... | @@ -486,6 +515,15 @@ pub const RefList = struct { |
| 486 | 515 | } |
| 487 | 516 | }; |
| 488 | 517 | |
| 518 | pub const OptionalRefList = struct { | |
| 519 | start: u32, | |
| 520 | len: u32, | |
| 521 | ||
| 522 | pub fn slice(self: OptionalRefList, teg: *const Teg) []const Inst.Ref.Optional { | |
| 523 | return @ptrCast(teg.extra.list.items[self.start..][0..self.len]); | |
| 524 | } | |
| 525 | }; | |
| 526 | ||
| 489 | 527 | pub const IndexList = struct { |
| 490 | 528 | start: u32, |
| 491 | 529 | len: u32, |
| ... | ... | @@ -524,6 +562,10 @@ pub fn instData(teg: *const Teg, comptime tag: Teg.Tag, index: Inst.Index) tag.D |
| 524 | 562 | return teg.instEncodedData(index).decode(tag, teg.instTag(index)); |
| 525 | 563 | } |
| 526 | 564 | |
| 565 | pub fn instDataUnsafe(teg: *const Teg, comptime T: type, index: Inst.Index) T { | |
| 566 | return teg.instEncodedData(index).decodeUnsafe(T, teg.instTag(index)); | |
| 567 | } | |
| 568 | ||
| 527 | 569 | pub fn instPayload(teg: *const Teg, comptime tag: Teg.Tag, index: Inst.Index) tag.Data().T { |
| 528 | 570 | return teg.instData(tag, index).extra_index.get(teg); |
| 529 | 571 | } |
| ... | ... | @@ -554,52 +596,31 @@ pub fn fullFuncDecl(teg: *const Teg, index: Inst.Index) struct { Teg.Decl, Teg.D |
| 554 | 596 | return .{ decl, decl_fn }; |
| 555 | 597 | } |
| 556 | 598 | |
| 557 | pub const FullDecl = struct { | |
| 558 | name: NullTerminatedString, | |
| 559 | hash: Cache.BinDigest, | |
| 560 | instruction_count: u32, | |
| 561 | data: Data, | |
| 562 | body: Inst.Ref, | |
| 563 | ||
| 564 | const Data = union(enum) { | |
| 565 | function: Teg.DeclFn, | |
| 566 | immutable: struct {}, | |
| 567 | }; | |
| 568 | }; | |
| 569 | ||
| 570 | pub fn fullDecl(teg: *const Teg, index: Inst.Index) FullDecl { | |
| 571 | const decl, var trail = teg.instData(.declaration, index).extra_index.getTrail(teg); | |
| 599 | pub fn fullDecl(teg: *const Teg, index: Inst.Index) Decl { | |
| 600 | const decl = teg.instEncodedData(index).decodeUnsafe(PlNode(Decl), teg.instTag(index)).extra_index.get(teg); | |
| 572 | 601 | return .{ |
| 573 | 602 | .name = decl.name, |
| 574 | 603 | .hash = decl.hash, |
| 575 | 604 | .instruction_count = decl.instruction_count, |
| 576 | 605 | .body = decl.body, |
| 577 | .data = switch (decl.kind) { | |
| 578 | inline else => |tag| @unionInit( | |
| 579 | FullDecl.Data, | |
| 580 | @tagName(tag), | |
| 581 | trail.read(&teg.extra, std.meta.FieldType( | |
| 582 | FullDecl.Data, | |
| 583 | @field(std.meta.FieldEnum(FullDecl.Data), @tagName(tag)), | |
| 584 | )), | |
| 585 | ), | |
| 586 | }, | |
| 587 | 606 | }; |
| 588 | 607 | } |
| 589 | 608 | |
| 590 | 609 | pub fn declData(teg: *const Teg, index: Inst.Index) Teg.Decl { |
| 591 | return teg.instData(.declaration, index).extra_index.get(teg); | |
| 610 | return teg.instDataUnsafe(PlNode(Decl), index).extra_index.get(teg); | |
| 592 | 611 | } |
| 593 | 612 | |
| 594 | 613 | pub fn moduleDeclarations(teg: *const Teg, module_index: Inst.Index) []const Inst.Index { |
| 595 | 614 | const module = teg.instData(.module, module_index); |
| 596 | 615 | const module_data: IndexList = module.extra_index.get(teg); |
| 597 | 616 | const slice = module_data.slice(teg); |
| 598 | if (std.debug.runtime_safety) { | |
| 617 | if (core.runtime_safety) { | |
| 599 | 618 | for (slice) |item| { |
| 600 | if (teg.instTag(item) != .declaration) { | |
| 601 | std.debug.print("\ngot unknown tag {s}\n", .{@tagName(teg.instTag(item))}); | |
| 602 | unreachable; | |
| 619 | switch (teg.instTag(item)) { | |
| 620 | .declaration, .variable_declaration, .test_declaration => {}, | |
| 621 | else => { | |
| 622 | std.debug.panic("\ngot unknown tag {s}\n", .{@tagName(teg.instTag(item))}); | |
| 623 | }, | |
| 603 | 624 | } |
| 604 | 625 | } |
| 605 | 626 | } |
| ... | ... | @@ -614,8 +635,8 @@ pub fn rootFunction(teg: *const Teg) Inst.Index { |
| 614 | 635 | } |
| 615 | 636 | |
| 616 | 637 | pub fn declName(teg: *const Teg, decl_index: Inst.Index) []const u8 { |
| 617 | const decl = teg.instData(.declaration, decl_index); | |
| 618 | const decl_data: Teg.Decl = decl.extra_index.get(teg); | |
| 638 | const decl = teg.instDataUnsafe(PlNode(Decl), decl_index); | |
| 639 | const decl_data: Decl = decl.extra_index.get(teg); | |
| 619 | 640 | return decl_data.name.slice(teg.string_bytes); |
| 620 | 641 | } |
| 621 | 642 | |
| ... | ... | @@ -623,11 +644,306 @@ pub fn dumpToWriter(teg: Teg, writer: anytype, tty: ?std.io.tty.Config) !void { |
| 623 | 644 | try writer.print("{}", .{Dump{ .teg = teg, .tty = tty }}); |
| 624 | 645 | } |
| 625 | 646 | |
| 647 | /// Used for generating decl hashes | |
| 648 | fn isolatedRefValue(decl_index: Inst.Index, ref_to_hash: Inst.Ref) i32 { | |
| 649 | return switch (ref_to_hash.decode()) { | |
| 650 | .value => |value| -@as(i32, @intCast(@intFromEnum(value))), | |
| 651 | .index => |index| @intCast(@intFromEnum(index) - @intFromEnum(decl_index)), | |
| 652 | }; | |
| 653 | } | |
| 654 | ||
| 655 | /// Used for generating decl hashes | |
| 656 | fn isolatedIndexValue(decl_index: Inst.Index, index_to_hash: Inst.Index) u32 { | |
| 657 | return @intFromEnum(index_to_hash) - @intFromEnum(decl_index); | |
| 658 | } | |
| 659 | ||
| 660 | pub fn writeToHasher( | |
| 661 | teg: *const Teg, | |
| 662 | hasher: *HashHelper, | |
| 663 | tag_list: []Tag, | |
| 664 | data_list: []EncodedData, | |
| 665 | decl_index: Inst.Index, | |
| 666 | ) void { | |
| 667 | // Add all of the tags | |
| 668 | hasher.addBytes(std.mem.sliceAsBytes(tag_list)); | |
| 669 | ||
| 670 | // Add all of the data | |
| 671 | for (tag_list, data_list) |tag, data| { | |
| 672 | ||
| 673 | // This should roughly line up with Teg.Tag.Data | |
| 674 | switch (tag) { | |
| 675 | // PlNode(IndexList), | |
| 676 | .module, | |
| 677 | .block, | |
| 678 | .@"comptime", | |
| 679 | => { | |
| 680 | const pl_node = data.decodeUnsafe(PlNode(IndexList), tag); | |
| 681 | hasher.add(pl_node.src_node); | |
| 682 | writeIndexListToHasher(hasher, pl_node.extra_index.get(teg).slice(teg), decl_index); | |
| 683 | }, | |
| 684 | ||
| 685 | // PlNode | |
| 686 | inline .declaration, | |
| 687 | .variable_declaration, | |
| 688 | .test_declaration, | |
| 689 | .coercion, | |
| 690 | .@"break", | |
| 691 | .number, | |
| 692 | .string, | |
| 693 | .call, | |
| 694 | .@"if", | |
| 695 | .store, | |
| 696 | .js_new, | |
| 697 | .struct_decl, | |
| 698 | .struct_init, | |
| 699 | .array_init, | |
| 700 | .func, | |
| 701 | .store_decl, | |
| 702 | .store_field, | |
| 703 | => |pl_node_tag| { | |
| 704 | const pl_node = data.decode(pl_node_tag, tag); | |
| 705 | hasher.add(pl_node.src_node); | |
| 706 | teg.writePlNodeInnerToHasher(hasher, pl_node_tag.Data().T, pl_node.extra_index.get(teg), decl_index); | |
| 707 | }, | |
| 708 | // StrNode | |
| 709 | .load_decl => { | |
| 710 | const str_node = data.decodeUnsafe(StrNode, tag); | |
| 711 | hasher.add(str_node.src_node); | |
| 712 | hasher.addBytes(str_node.string.slice(teg.string_bytes)); | |
| 713 | }, | |
| 714 | // DataNode | |
| 715 | .param => { | |
| 716 | const data_node = data.decodeUnsafe(DataNode, tag); | |
| 717 | hasher.add(data_node.src_node); | |
| 718 | hasher.add(data_node.data); | |
| 719 | }, | |
| 720 | // RefNode | |
| 721 | .memory, | |
| 722 | .discard, | |
| 723 | .ensure_void, | |
| 724 | .negate, | |
| 725 | .bool_not, | |
| 726 | .bit_not, | |
| 727 | .@"extern", | |
| 728 | .import, | |
| 729 | .fetch, | |
| 730 | .compile_error, | |
| 731 | .type_of, | |
| 732 | .array_type, | |
| 733 | .assert, | |
| 734 | => { | |
| 735 | const ref_node = data.decodeUnsafe(RefNode, tag); | |
| 736 | hasher.add(ref_node.src_node); | |
| 737 | hasher.add(isolatedRefValue(decl_index, ref_node.operand)); | |
| 738 | }, | |
| 739 | // PlNode(Bin) | |
| 740 | .field, | |
| 741 | .index, | |
| 742 | .add, | |
| 743 | .sub, | |
| 744 | .mul, | |
| 745 | .div, | |
| 746 | .rem, | |
| 747 | .pow, | |
| 748 | .@"and", | |
| 749 | .@"or", | |
| 750 | .bit_and, | |
| 751 | .bit_or, | |
| 752 | .bit_xor, | |
| 753 | .shl, | |
| 754 | .shr, | |
| 755 | .eq, | |
| 756 | .ne, | |
| 757 | .lt, | |
| 758 | .le, | |
| 759 | .gt, | |
| 760 | .ge, | |
| 761 | => { | |
| 762 | const pl_node = data.decodeUnsafe(PlNode(Bin), tag); | |
| 763 | const bin = pl_node.extra_index.get(teg); | |
| 764 | hasher.add(isolatedRefValue(decl_index, bin.lhs)); | |
| 765 | hasher.add(isolatedRefValue(decl_index, bin.rhs)); | |
| 766 | }, | |
| 767 | // Result Types | |
| 768 | .result_type_argument => { | |
| 769 | const arg = data.decodeUnsafe(ResultTypeArgument, tag); | |
| 770 | hasher.add(isolatedRefValue(decl_index, arg.call_base)); | |
| 771 | hasher.add(arg.argument); | |
| 772 | }, | |
| 773 | .result_type_struct_field => { | |
| 774 | const struct_field = data.decodeUnsafe(ResultTypeStructField, tag); | |
| 775 | hasher.add(isolatedRefValue(decl_index, struct_field.type)); | |
| 776 | hasher.addBytes(struct_field.field.slice(teg.string_bytes)); | |
| 777 | }, | |
| 778 | .result_type_tuple_field => { | |
| 779 | const tuple_field = data.decodeUnsafe(ResultTypeTupleField, tag); | |
| 780 | hasher.add(isolatedRefValue(decl_index, tuple_field.type)); | |
| 781 | hasher.add(tuple_field.item); | |
| 782 | }, | |
| 783 | .result_type_array_value => { | |
| 784 | const array_value = data.decodeUnsafe(ResultTypeArrayValue, tag); | |
| 785 | hasher.add(isolatedRefValue(decl_index, array_value.type)); | |
| 786 | }, | |
| 787 | } | |
| 788 | } | |
| 789 | } | |
| 790 | ||
| 791 | fn writeIndexListToHasher( | |
| 792 | hasher: *HashHelper, | |
| 793 | index_list: []const Inst.Index, | |
| 794 | decl_index: Inst.Index, | |
| 795 | ) void { | |
| 796 | hasher.add(index_list.len); | |
| 797 | for (index_list) |idx| { | |
| 798 | hasher.add(isolatedIndexValue(decl_index, idx)); | |
| 799 | } | |
| 800 | } | |
| 801 | ||
| 802 | fn writeRefListToHasher( | |
| 803 | hasher: *HashHelper, | |
| 804 | decl_index: Inst.Index, | |
| 805 | index_list: []const Inst.Ref, | |
| 806 | ) void { | |
| 807 | hasher.add(index_list.len); | |
| 808 | for (index_list) |idx| { | |
| 809 | hasher.add(isolatedRefValue(decl_index, idx)); | |
| 810 | } | |
| 811 | } | |
| 812 | ||
| 813 | fn writePlNodeInnerToHasher( | |
| 814 | teg: *const Teg, | |
| 815 | hasher: *HashHelper, | |
| 816 | comptime T: type, | |
| 817 | data: T, | |
| 818 | decl_index: Inst.Index, | |
| 819 | ) void { | |
| 820 | switch (T) { | |
| 821 | Decl => { | |
| 822 | hasher.addBytes(&data.hash); | |
| 823 | hasher.addBytes(data.name.slice(teg.string_bytes)); | |
| 824 | }, | |
| 825 | Float => { | |
| 826 | const as_bytes: [2]u32 = .{ | |
| 827 | data.low, | |
| 828 | data.high, | |
| 829 | }; | |
| 830 | hasher.add(@as(u64, @bitCast(as_bytes))); | |
| 831 | }, | |
| 832 | SlicedString => { | |
| 833 | hasher.addBytes(data.slice(teg.string_bytes)); | |
| 834 | }, | |
| 835 | Coercion => { | |
| 836 | hasher.add(isolatedRefValue(decl_index, data.type)); | |
| 837 | hasher.add(isolatedRefValue(decl_index, data.operand)); | |
| 838 | }, | |
| 839 | Break => { | |
| 840 | hasher.add(isolatedIndexValue(decl_index, data.block)); | |
| 841 | hasher.add(isolatedRefValue(decl_index, data.operand)); | |
| 842 | }, | |
| 843 | Call => { | |
| 844 | hasher.add(isolatedRefValue(decl_index, data.base)); | |
| 845 | writeRefListToHasher(hasher, decl_index, data.args.slice(teg)); | |
| 846 | }, | |
| 847 | If => { | |
| 848 | hasher.add(isolatedRefValue(decl_index, data.cond)); | |
| 849 | hasher.add(isolatedRefValue(decl_index, data.yes)); | |
| 850 | hasher.add(isolatedRefValue(decl_index, data.no)); | |
| 851 | }, | |
| 852 | Store => { | |
| 853 | hasher.add(isolatedIndexValue(decl_index, data.memory)); | |
| 854 | hasher.add(isolatedRefValue(decl_index, data.operand)); | |
| 855 | }, | |
| 856 | StoreField => { | |
| 857 | hasher.add(isolatedRefValue(decl_index, data.base)); | |
| 858 | hasher.add(isolatedRefValue(decl_index, data.field)); | |
| 859 | hasher.add(isolatedRefValue(decl_index, data.operand)); | |
| 860 | }, | |
| 861 | StoreVarDecl => { | |
| 862 | hasher.addBytes(data.var_decl.slice(teg.string_bytes)); | |
| 863 | hasher.add(isolatedRefValue(decl_index, data.operand)); | |
| 864 | }, | |
| 865 | JsNew => { | |
| 866 | hasher.add(isolatedRefValue(decl_index, data.base)); | |
| 867 | writeRefListToHasher(hasher, decl_index, data.args.slice(teg)); | |
| 868 | }, | |
| 869 | StructDecl => { | |
| 870 | hasher.add(data.field_len); | |
| 871 | for (data.keys(teg), data.types(teg)) |key, ty| { | |
| 872 | hasher.addBytes(key.slice(teg.string_bytes)); | |
| 873 | hasher.add(isolatedRefValue(decl_index, ty)); | |
| 874 | } | |
| 875 | }, | |
| 876 | StructInit => { | |
| 877 | hasher.add(isolatedRefValue(decl_index, data.base_type)); | |
| 878 | hasher.add(data.field_len); | |
| 879 | for (data.keys(teg), data.inits(teg)) |key, init| { | |
| 880 | hasher.addBytes(key.slice(teg.string_bytes)); | |
| 881 | hasher.add(isolatedRefValue(decl_index, init)); | |
| 882 | } | |
| 883 | }, | |
| 884 | ArrayInit => { | |
| 885 | if (data.type_hint.unwrap()) |type_hint| | |
| 886 | hasher.add(isolatedRefValue(decl_index, type_hint)) | |
| 887 | else | |
| 888 | hasher.add(@as(i32, std.math.maxInt(i32))); | |
| 889 | writeRefListToHasher(hasher, decl_index, data.list.slice(teg)); | |
| 890 | }, | |
| 891 | Func => { | |
| 892 | for (data.params.slice(teg)) |optional| { | |
| 893 | if (optional.unwrap()) |item| | |
| 894 | hasher.add(isolatedRefValue(decl_index, item)); | |
| 895 | } | |
| 896 | ||
| 897 | if (data.return_type.unwrap()) |type_hint| | |
| 898 | hasher.add(isolatedRefValue(decl_index, type_hint)) | |
| 899 | else | |
| 900 | hasher.add(@as(i32, std.math.maxInt(i32))); | |
| 901 | ||
| 902 | hasher.add(isolatedIndexValue(decl_index, data.body)); | |
| 903 | hasher.add(data.instruction_count); | |
| 904 | }, | |
| 905 | else => @compileError("hash not implemented for " ++ @typeName(T)), | |
| 906 | } | |
| 907 | } | |
| 908 | ||
| 909 | pub const SerializedHeader = extern struct { | |
| 910 | const version = @import("config").version_string ++ "\x00"; | |
| 911 | ||
| 912 | instruction_len: u32, | |
| 913 | string_bytes: u32, | |
| 914 | extra_len: u32, | |
| 915 | report_err_len: u32, | |
| 916 | report_warn_len: u32, | |
| 917 | }; | |
| 918 | ||
| 919 | pub fn serialize(teg: Teg, target: core.SerializeTarget) !void { | |
| 920 | const header: SerializedHeader = .{ | |
| 921 | .instruction_len = @intCast(teg.insts.len), | |
| 922 | .string_bytes = @intCast(teg.string_bytes.len), | |
| 923 | .extra_len = @intCast(teg.extra.list.items.len), | |
| 924 | .report_err_len = @intCast(teg.report.errors.len), | |
| 925 | .report_warn_len = @intCast(teg.report.warnings.len), | |
| 926 | }; | |
| 927 | ||
| 928 | var iovecs = [_]std.posix.iovec_const{ | |
| 929 | core.iovecConst(SerializedHeader.version), | |
| 930 | core.iovecConst(std.mem.asBytes(&header)), | |
| 931 | core.iovecConst(std.mem.sliceAsBytes(teg.insts.items(.tag))), | |
| 932 | core.iovecConst(std.mem.sliceAsBytes(teg.insts.items(.data))), | |
| 933 | core.iovecConst(teg.string_bytes), | |
| 934 | core.iovecConst(std.mem.sliceAsBytes(teg.extra.list.items)), | |
| 935 | core.iovecConst(std.mem.sliceAsBytes(teg.report.errors)), | |
| 936 | core.iovecConst(std.mem.sliceAsBytes(teg.report.warnings)), | |
| 937 | }; | |
| 938 | try target.writev(&iovecs); | |
| 939 | } | |
| 940 | ||
| 626 | 941 | const Teg = @This(); |
| 627 | 942 | |
| 628 | 943 | const std = @import("std"); |
| 629 | 944 | const mem = std.mem; |
| 630 | 945 | const assert = std.debug.assert; |
| 946 | const HashHelper = std.Build.Cache.HashHelper; | |
| 631 | 947 | |
| 632 | 948 | const core = @import("mod.zig"); |
| 633 | 949 | const Cache = core.Cache; |
| ... | ... | @@ -639,9 +955,6 @@ const TokenIndex = Ast.TokenIndex; |
| 639 | 955 | const OptionalNodeIndex = Ast.OptionalNodeIndex; |
| 640 | 956 | const OptionalTokenIndex = Ast.OptionalTokenIndex; |
| 641 | 957 | const OptionalExtraIndex = Ast.OptionalExtraIndex; |
| 642 | const optionalTokenIndex = OptionalTokenIndex.init; | |
| 643 | const optionalNodeIndex = OptionalNodeIndex.init; | |
| 644 | const optionalExtraIndex = OptionalExtraIndex.init; | |
| 645 | 958 | |
| 646 | 959 | const dod = core.array; |
| 647 | 960 | const Float = dod.Float; |
src/core/Tokenizer.zig+93-52| ... | ... | @@ -9,13 +9,8 @@ code: [:0]const u8, |
| 9 | 9 | pos: ByteOffset = 0, |
| 10 | 10 | |
| 11 | 11 | /// Tokenize source code into a list of tokens. |
| 12 | /// When an error occurs, the position of the error is returned in `error_pos`. | |
| 13 | 12 | /// Caller owns returned slice. |
| 14 | pub fn tokenize( | |
| 15 | source_code: [:0]const u8, | |
| 16 | gpa: mem.Allocator, | |
| 17 | error_pos: *ByteOffset, | |
| 18 | ) TokenizeError!Token.List.Slice { | |
| 13 | pub fn tokenize(source_code: [:0]const u8, gpa: mem.Allocator) mem.Allocator.Error!Token.List.Slice { | |
| 19 | 14 | const trace = T.trace(@src()); |
| 20 | 15 | defer trace.end(); |
| 21 | 16 | |
| ... | ... | @@ -25,11 +20,10 @@ pub fn tokenize( |
| 25 | 20 | // pre-allocate some space based on a very rough guess |
| 26 | 21 | try tokens.ensureTotalCapacity(gpa, @divFloor(source_code.len, 8)); |
| 27 | 22 | |
| 28 | var tokenizer: Tokenizer = .{ .code = source_code, .pos = 0 }; | |
| 29 | errdefer error_pos.* = tokenizer.pos; | |
| 23 | var tokenizer: Tokenizer = .{ .code = source_code }; | |
| 30 | 24 | |
| 31 | 25 | tokenizer.skipWhitespace(); |
| 32 | while (try tokenizer.next()) |token| { | |
| 26 | while (tokenizer.next()) |token| { | |
| 33 | 27 | try tokens.append(gpa, token); |
| 34 | 28 | tokenizer.skipWhitespace(); |
| 35 | 29 | } |
| ... | ... | @@ -142,6 +136,11 @@ pub const Token = struct { |
| 142 | 136 | /// tokenization, parsing will begin, and an error will be reported when |
| 143 | 137 | /// this token is hit. That way, the error can tell you what it expected. |
| 144 | 138 | invalid, |
| 139 | invalid_number, | |
| 140 | invalid_string, | |
| 141 | invalid_unterminated_string, | |
| 142 | invalid_git_conflict, | |
| 143 | invalid_utf8, | |
| 145 | 144 | /// End of file |
| 146 | 145 | /// Used so that parser does not need to do out of bounds checks |
| 147 | 146 | eof, |
| ... | ... | @@ -149,7 +148,16 @@ pub const Token = struct { |
| 149 | 148 | /// Most tokens have statically known lexemes, such as "struct" or "+=" |
| 150 | 149 | pub fn lexeme(tag: Tag) ?[]const u8 { |
| 151 | 150 | return switch (tag) { |
| 152 | .invalid => unreachable, | |
| 151 | .eof => unreachable, // always must be special cased | |
| 152 | ||
| 153 | // invalid sequences do not have static lexemes | |
| 154 | .invalid, | |
| 155 | .invalid_number, | |
| 156 | .invalid_string, | |
| 157 | .invalid_unterminated_string, | |
| 158 | .invalid_git_conflict, | |
| 159 | .invalid_utf8, | |
| 160 | => null, | |
| 153 | 161 | |
| 154 | 162 | // do not have a static lexeme |
| 155 | 163 | .id, |
| ... | ... | @@ -162,8 +170,6 @@ pub const Token = struct { |
| 162 | 170 | .module_doc_comment, |
| 163 | 171 | => null, |
| 164 | 172 | |
| 165 | .eof => unreachable, // always must be special cased | |
| 166 | ||
| 167 | 173 | .@"and" => "&", |
| 168 | 174 | .and_and => "&&", |
| 169 | 175 | .bang => "!", |
| ... | ... | @@ -226,8 +232,15 @@ pub const Token = struct { |
| 226 | 232 | |
| 227 | 233 | pub fn description(tag: Tag) []const u8 { |
| 228 | 234 | return switch (tag) { |
| 229 | .invalid => unreachable, | |
| 230 | 235 | .eof => "end of file", |
| 236 | ||
| 237 | .invalid => "unknown character", | |
| 238 | .invalid_number => "invalid number", | |
| 239 | .invalid_string => "invalid string literal", | |
| 240 | .invalid_unterminated_string => "unterminated string literal", | |
| 241 | .invalid_git_conflict => "git merge conflict marker", | |
| 242 | .invalid_utf8 => "invalid UTF-8 bytes", | |
| 243 | ||
| 231 | 244 | .id => "an identifier", |
| 232 | 245 | .builtin_id => "a builtin identifier", |
| 233 | 246 | .number_int => "a numeric literal", |
| ... | ... | @@ -242,7 +255,7 @@ pub const Token = struct { |
| 242 | 255 | |
| 243 | 256 | pub fn isKeyword(tag: Tag) bool { |
| 244 | 257 | return switch (tag) { |
| 245 | inline else => |t| mem.startsWith(u8, @tagName(t), "kw_"), | |
| 258 | inline else => |t| comptime mem.startsWith(u8, @tagName(t), "kw_"), | |
| 246 | 259 | }; |
| 247 | 260 | } |
| 248 | 261 | |
| ... | ... | @@ -250,6 +263,13 @@ pub const Token = struct { |
| 250 | 263 | pub fn staticLen(tag: Tag) ?u32 { |
| 251 | 264 | return if (tag.lexeme()) |str| @intCast(str.len) else null; |
| 252 | 265 | } |
| 266 | ||
| 267 | pub fn isInvalid(tag: Tag) bool { | |
| 268 | return switch (tag) { | |
| 269 | .invalid => true, | |
| 270 | inline else => |t| comptime mem.startsWith(u8, @tagName(t), "invalid_"), | |
| 271 | }; | |
| 272 | } | |
| 253 | 273 | }; |
| 254 | 274 | |
| 255 | 275 | /// Instead of storing the length of a token, we can recompute this |
| ... | ... | @@ -258,15 +278,27 @@ pub const Token = struct { |
| 258 | 278 | /// If you are computing the length of something that is knowable |
| 259 | 279 | /// without the source code, use token.tag.staticLen() instead. |
| 260 | 280 | pub fn len(token: Token, utf8_source: [:0]const u8) u32 { |
| 261 | return token.tag.staticLen() orelse { | |
| 262 | var tokenizer = Tokenizer{ .code = utf8_source, .pos = token.offset }; | |
| 263 | assert( | |
| 264 | (tokenizer.next() catch | |
| 265 | unreachable // data corruption | |
| 266 | orelse unreachable // data corruption | |
| 267 | ).tag == token.tag, | |
| 268 | ); | |
| 269 | return tokenizer.pos - token.offset; | |
| 281 | return token.tag.staticLen() orelse switch (token.tag) { | |
| 282 | ||
| 283 | // TODO: calling len on these tokens is incorrect, as | |
| 284 | // error handling code should special case these all | |
| 285 | // to highlight exactly what went wrong. | |
| 286 | .invalid => std.unicode.utf8ByteSequenceLength(utf8_source[token.offset]) catch 1, | |
| 287 | .invalid_git_conflict => 7, | |
| 288 | .invalid_utf8 => 1, | |
| 289 | ||
| 290 | else => { | |
| 291 | @branchHint(.likely); | |
| 292 | var tokenizer = Tokenizer{ .code = utf8_source, .pos = token.offset }; | |
| 293 | const retokenized = tokenizer.next() orelse unreachable; // data corruption | |
| 294 | if (retokenized.tag != token.tag and !token.tag.isInvalid()) { | |
| 295 | std.debug.panic( | |
| 296 | "len() got wrong tag when re-tokenizing: .{s} != .{s}", | |
| 297 | .{ @tagName(retokenized.tag), @tagName(token.tag) }, | |
| 298 | ); | |
| 299 | } | |
| 300 | return tokenizer.pos - token.offset; | |
| 301 | }, | |
| 270 | 302 | }; |
| 271 | 303 | } |
| 272 | 304 | |
| ... | ... | @@ -275,7 +307,7 @@ pub const Token = struct { |
| 275 | 307 | utf8_source[token.offset..][0..token.len(utf8_source)]; |
| 276 | 308 | } |
| 277 | 309 | |
| 278 | const keywords = std.StaticStringMap(Tag).initComptime(keywords: { | |
| 310 | pub const keywords = std.StaticStringMap(Tag).initComptime(keywords: { | |
| 279 | 311 | const fields = std.enums.values(Tag); |
| 280 | 312 | const KV = struct { []const u8, Tag }; |
| 281 | 313 | var kws: [fields.len]KV = undefined; |
| ... | ... | @@ -290,8 +322,8 @@ pub const Token = struct { |
| 290 | 322 | }); |
| 291 | 323 | }; |
| 292 | 324 | |
| 293 | /// When any of these errors occurs, the tokenizer's position is set to | |
| 294 | /// the start of the offending token. | |
| 325 | /// When any of these errors occurs, the tokenizer's | |
| 326 | /// position is set to the start of the offending bytes. | |
| 295 | 327 | pub const NextTokenError = error{ |
| 296 | 328 | /// Generic token syntax error |
| 297 | 329 | InvalidSyntax, |
| ... | ... | @@ -310,11 +342,34 @@ pub const NextTokenError = error{ |
| 310 | 342 | InvalidUtf8, |
| 311 | 343 | }; |
| 312 | 344 | |
| 313 | pub const TokenizeError = mem.Allocator.Error || NextTokenError; | |
| 314 | ||
| 315 | 345 | /// Next assumes the position is already at the start of a token. |
| 316 | 346 | /// You must call `skipWhitespace` before calling this. |
| 317 | pub fn next(t: *Tokenizer) !?Token { | |
| 347 | pub fn next(t: *Tokenizer) ?Token { | |
| 348 | return t.nextErrorable() catch |err| { | |
| 349 | defer { | |
| 350 | t.pos = @intCast(mem.indexOfScalarPos( | |
| 351 | u8, | |
| 352 | t.code, | |
| 353 | t.pos + 3, | |
| 354 | '\n', | |
| 355 | ) orelse t.code.len); | |
| 356 | } | |
| 357 | return .{ | |
| 358 | .offset = t.pos, | |
| 359 | .tag = switch (err) { | |
| 360 | error.InvalidSyntax => .invalid, | |
| 361 | error.TrailingNumericSeparator => .invalid_number, | |
| 362 | error.UnterminatedString => .invalid_unterminated_string, | |
| 363 | error.InvalidEscapeSequence => .invalid_string, | |
| 364 | error.InvalidEscapeCodepoint => .invalid_string, | |
| 365 | error.GitConflict => .invalid_git_conflict, | |
| 366 | error.InvalidUtf8 => .invalid_utf8, | |
| 367 | }, | |
| 368 | }; | |
| 369 | }; | |
| 370 | } | |
| 371 | ||
| 372 | pub fn nextErrorable(t: *Tokenizer) NextTokenError!?Token { | |
| 318 | 373 | while (true) { |
| 319 | 374 | const trace = T.trace(@src()); |
| 320 | 375 | defer trace.end(); |
| ... | ... | @@ -935,7 +990,7 @@ fn simpleToken(t: *Tokenizer, comptime tag: Token.Tag) Token { |
| 935 | 990 | /// However, the character we ran into might actually be invalid UTF-8. |
| 936 | 991 | /// This utility just checks for that to make sure we don't mask error.InvalidUtf8 |
| 937 | 992 | fn maybeUTF8Error(t: *Tokenizer, comptime real_err: anytype) @Type(.{ |
| 938 | .ErrorSet = &[_]std.builtin.Type.Error{ | |
| 993 | .error_set = &[_]std.builtin.Type.Error{ | |
| 939 | 994 | .{ .name = "InvalidUtf8" }, |
| 940 | 995 | .{ .name = @errorName(real_err) }, |
| 941 | 996 | }, |
| ... | ... | @@ -950,7 +1005,7 @@ fn maybeUTF8Error(t: *Tokenizer, comptime real_err: anytype) @Type(.{ |
| 950 | 1005 | } |
| 951 | 1006 | |
| 952 | 1007 | fn maybeUTF8ErrorAt(t: *Tokenizer, comptime real_err: anytype, at: u32) @Type(.{ |
| 953 | .ErrorSet = &[_]std.builtin.Type.Error{ | |
| 1008 | .error_set = &[_]std.builtin.Type.Error{ | |
| 954 | 1009 | .{ .name = "InvalidUtf8" }, |
| 955 | 1010 | .{ .name = @errorName(real_err) }, |
| 956 | 1011 | }, |
| ... | ... | @@ -1240,10 +1295,10 @@ test "basic tokenizer example" { |
| 1240 | 1295 | var tokenizer = Tokenizer{ .code = source }; |
| 1241 | 1296 | for (expected_tokens) |tok| { |
| 1242 | 1297 | tokenizer.skipWhitespace(); |
| 1243 | try testing.expectEqual(tok, (try tokenizer.next()).?); | |
| 1298 | try testing.expectEqual(tok, tokenizer.next().?); | |
| 1244 | 1299 | } |
| 1245 | 1300 | tokenizer.skipWhitespace(); |
| 1246 | try testing.expectEqual(null, (try tokenizer.next())); | |
| 1301 | try testing.expectEqual(null, tokenizer.next()); | |
| 1247 | 1302 | } |
| 1248 | 1303 | |
| 1249 | 1304 | test "field access" { |
| ... | ... | @@ -1254,25 +1309,11 @@ test "field access" { |
| 1254 | 1309 | \\.0 hello.1.2 |
| 1255 | 1310 | ; |
| 1256 | 1311 | const expected_tokens = [_]Token{ |
| 1257 | .{ .tag = .kw_var, .offset = 0 }, | |
| 1258 | .{ .tag = .id, .offset = 4 }, | |
| 1259 | .{ .tag = .equal, .offset = 10 }, | |
| 1260 | .{ .tag = .number_float, .offset = 12 }, | |
| 1261 | .{ .tag = .number_int, .offset = 20 }, | |
| 1262 | .{ .tag = .semi, .offset = 21 }, | |
| 1263 | .{ .tag = .kw_pub, .offset = 42 }, | |
| 1264 | .{ .tag = .kw_fn, .offset = 46 }, | |
| 1265 | .{ .tag = .id, .offset = 49 }, | |
| 1266 | .{ .tag = .start_paren, .offset = 54 }, | |
| 1267 | .{ .tag = .colon, .offset = 55 }, | |
| 1268 | .{ .tag = .plus, .offset = 56 }, | |
| 1269 | .{ .tag = .id, .offset = 58 }, | |
| 1270 | .{ .tag = .end_paren, .offset = 62 }, | |
| 1271 | .{ .tag = .id, .offset = 64 }, | |
| 1272 | .{ .tag = .kw_return, .offset = 67 }, | |
| 1273 | .{ .tag = .id, .offset = 74 }, | |
| 1274 | .{ .tag = .id, .offset = 77 }, | |
| 1275 | .{ .tag = .string, .offset = 86 }, | |
| 1312 | .{ .tag = .dot, .offset = 0 }, | |
| 1313 | .{ .tag = .number_int, .offset = 1 }, | |
| 1314 | .{ .tag = .id, .offset = 3 }, | |
| 1315 | .{ .tag = .dot, .offset = 8 }, | |
| 1316 | .{ .tag = .number_float, .offset = 9 }, | |
| 1276 | 1317 | }; |
| 1277 | 1318 | var tokenizer = Tokenizer{ .code = source }; |
| 1278 | 1319 | for (expected_tokens) |tok| { |
src/core/Transform.zig+681-381| ... | ... | @@ -18,41 +18,46 @@ ast: Ast, |
| 18 | 18 | // these values are allocated through `gpa`, so that resulting IR can be |
| 19 | 19 | // trivially shrunk before returning. |
| 20 | 20 | |
| 21 | insts: Inst.List = .{}, | |
| 22 | extra: ExtraStore = .{}, | |
| 23 | string_bytes: std.ArrayListUnmanaged(u8) = .{}, | |
| 21 | insts: Inst.List, | |
| 22 | extra: ExtraStore, | |
| 23 | string_bytes: std.ArrayListUnmanaged(u8), | |
| 24 | 24 | report: Report.Builder, |
| 25 | 25 | |
| 26 | 26 | // however, these are allocated through `arena`, since they are always freed at |
| 27 | 27 | // the end of the pass. |
| 28 | 28 | |
| 29 | scratch: core.U32Stack = .{}, | |
| 29 | scratch: core.U32Stack, | |
| 30 | 30 | id_decode_buf: std.ArrayList(u8), |
| 31 | scopes: std.ArrayListUnmanaged(Scope) = .{}, | |
| 31 | scopes: std.ArrayListUnmanaged(Scope), | |
| 32 | capture_scopes: std.ArrayListUnmanaged(CaptureScope), | |
| 33 | ||
| 34 | has_warned_top_level_statement: bool, | |
| 32 | 35 | |
| 33 | 36 | pub const Error = error{ |
| 34 | 37 | /// A `Report` will contain at least one error. |
| 35 | 38 | TransformFail, |
| 36 | 39 | } || Allocator.Error; |
| 37 | 40 | |
| 38 | pub fn transform( | |
| 39 | ast: Ast, | |
| 40 | gpa: Allocator, | |
| 41 | file: *Comp.File, | |
| 42 | fail_report: *Report, | |
| 43 | ) Error!Teg { | |
| 41 | pub fn transform(ast: Ast, gpa: Allocator, fail_report: *Report) Error!Teg { | |
| 44 | 42 | const trace = tracer.trace(@src()); |
| 45 | 43 | defer trace.end(); |
| 46 | 44 | |
| 47 | 45 | var sfa = std.heap.stackFallback(4096, gpa); |
| 48 | 46 | |
| 49 | 47 | var t: Transform = .{ |
| 50 | .ast = ast, | |
| 51 | 48 | .gpa = gpa, |
| 52 | // TODO: Currently this is illusion of free will | |
| 53 | 49 | .arena = gpa, |
| 54 | .report = .{ .gpa = gpa, .current_file = file }, | |
| 55 | .id_decode_buf = std.ArrayList(u8).initCapacity(sfa.get(), 4096) catch unreachable, | |
| 50 | .ast = ast, | |
| 51 | .insts = .{}, | |
| 52 | .extra = .empty, | |
| 53 | .string_bytes = .empty, | |
| 54 | .report = .init(gpa), | |
| 55 | .scratch = .empty, | |
| 56 | .scopes = .empty, | |
| 57 | .capture_scopes = .empty, | |
| 58 | .id_decode_buf = std.ArrayList(u8).initCapacity(sfa.get(), 4096) catch | |
| 59 | unreachable, // stack-fallback has enough space | |
| 60 | .has_warned_top_level_statement = false, | |
| 56 | 61 | }; |
| 57 | 62 | defer t.deinit(); |
| 58 | 63 | |
| ... | ... | @@ -83,7 +88,7 @@ fn deinit(t: *Transform) void { |
| 83 | 88 | t.id_decode_buf.deinit(); |
| 84 | 89 | t.scratch.list.deinit(t.arena); |
| 85 | 90 | t.scopes.deinit(t.arena); |
| 86 | t.id_decode_buf.deinit(); | |
| 91 | t.capture_scopes.deinit(t.arena); | |
| 87 | 92 | t.string_bytes.deinit(t.gpa); |
| 88 | 93 | t.insts.deinit(t.gpa); |
| 89 | 94 | t.extra.deinit(t.gpa); |
| ... | ... | @@ -120,6 +125,9 @@ const ResultType = union(enum) { |
| 120 | 125 | /// The expression is a field in an array initialization |
| 121 | 126 | array_value: Inst.Ref, |
| 122 | 127 | |
| 128 | // /// The expression is unknown field | |
| 129 | // field: Teg.ResultTypeField, | |
| 130 | ||
| 123 | 131 | /// This will return an instruction pointing to the the type |
| 124 | 132 | pub fn consume(result: *ResultType, t: *Transform) !?Inst.Ref { |
| 125 | 133 | if (result.* == .none) return null; |
| ... | ... | @@ -150,7 +158,7 @@ const BlockContext = struct { |
| 150 | 158 | break_target: ?*BreakTarget = null, |
| 151 | 159 | |
| 152 | 160 | const ReturnTarget = struct { |
| 153 | generated_block: ?Inst.Index = null, | |
| 161 | block_index: Inst.Index, | |
| 154 | 162 | result_type: ResultType, |
| 155 | 163 | }; |
| 156 | 164 | |
| ... | ... | @@ -166,14 +174,6 @@ const BlockContext = struct { |
| 166 | 174 | parent: ?*BreakTarget = null, |
| 167 | 175 | result_type: *ResultType, |
| 168 | 176 | |
| 169 | fn addChild(parent: *BreakTarget, label: ?[]const u8, is_loop: bool) BreakTarget { | |
| 170 | return .{ | |
| 171 | .parent = parent, | |
| 172 | .label = label, | |
| 173 | .is_loop = is_loop, | |
| 174 | }; | |
| 175 | } | |
| 176 | ||
| 177 | 177 | fn getInst(target: *BreakTarget, t: *Transform) !Inst.Index { |
| 178 | 178 | return target.block_index orelse { |
| 179 | 179 | const inst = try t.addUntracked(.block, undefined); |
| ... | ... | @@ -203,10 +203,11 @@ const BlockContext = struct { |
| 203 | 203 | return .{ |
| 204 | 204 | .decl_node_offset = self.decl_node_offset, |
| 205 | 205 | .return_target = return_target, |
| 206 | .break_target = null, | |
| 206 | 207 | }; |
| 207 | 208 | } |
| 208 | 209 | |
| 209 | fn withBreak(self: BlockContext, break_target: *BreakTarget) BlockContext { | |
| 210 | fn withBreak(self: BlockContext, break_target: ?*BreakTarget) BlockContext { | |
| 210 | 211 | return .{ |
| 211 | 212 | .decl_node_offset = self.decl_node_offset, |
| 212 | 213 | .return_target = self.return_target, |
| ... | ... | @@ -215,78 +216,29 @@ const BlockContext = struct { |
| 215 | 216 | } |
| 216 | 217 | |
| 217 | 218 | fn lookupNamedBreak(self: BlockContext, label: []const u8) ?*BreakTarget { |
| 218 | var break_target = self.break_target; | |
| 219 | while (break_target) |target| : (break_target = target.parent) { | |
| 219 | var target = self.break_target orelse return null; | |
| 220 | while (true) { | |
| 220 | 221 | if (target.label) |target_label| { |
| 221 | 222 | if (mem.eql(u8, label, target_label)) { |
| 222 | 223 | return target; |
| 223 | 224 | } |
| 224 | 225 | } |
| 226 | ||
| 227 | target = target.parent orelse return null; | |
| 225 | 228 | } |
| 226 | return null; | |
| 227 | 229 | } |
| 228 | 230 | }; |
| 229 | 231 | |
| 232 | // TODO: optimize this structure | |
| 233 | // | |
| 234 | // If Transform does not need to track more than a symbol list for scope | |
| 235 | // (BlockContext kind of does this, so it's likely), this can be refactored to a | |
| 236 | // custom structure which is a single array of { Hash, Key, Symbol }, where each | |
| 237 | // block context stores the index into this array where it's map starts. Lookups | |
| 238 | // are done by scanning the map backwards for a hash, which would allow for | |
| 239 | // trivial memory re-use. | |
| 230 | 240 | const Scope = struct { |
| 231 | symbols: std.StringArrayHashMapUnmanaged(Symbol) = .{}, | |
| 232 | ||
| 233 | // TODO: optimize this structure's size. | |
| 234 | const Symbol = struct { | |
| 235 | node: NodeIndex, | |
| 236 | data: Symbol.Data, | |
| 237 | used: bool = false, | |
| 238 | mutated: bool = false, | |
| 239 | discarded: ?NodeIndex = null, | |
| 240 | type: SymbolResultType, | |
| 241 | ||
| 242 | const Data = union(enum) { | |
| 243 | declaration, | |
| 244 | param, | |
| 245 | mutable: Inst.Index, | |
| 246 | immutable: Inst.Ref, | |
| 247 | }; | |
| 248 | ||
| 249 | const SymbolResultType = union(enum) { | |
| 250 | /// Points to the resolved result type (.ref), as is. | |
| 251 | present: Inst.Ref, | |
| 252 | /// This points to the value. Which means a `type_of` instruction | |
| 253 | /// must be inserted before it may be used. | |
| 254 | missing: Inst.Ref, | |
| 255 | ||
| 256 | pub fn resolve(srt: SymbolResultType) ResultType { | |
| 257 | return switch (srt) { | |
| 258 | .present => |type_ref| .{ .ref = type_ref }, | |
| 259 | .missing => |value_ref| .{ .type_of = value_ref }, | |
| 260 | }; | |
| 261 | } | |
| 262 | }; | |
| 263 | ||
| 264 | pub fn addWarnings(symbol: Symbol, t: *Transform) !void { | |
| 265 | if (!symbol.mutated and symbol.data == .mutable) { | |
| 266 | try t.report.warn(.not_mutated_var, .{ | |
| 267 | .node = symbol.node, | |
| 268 | }); | |
| 269 | } | |
| 270 | if (!symbol.used) { | |
| 271 | try t.report.warn(.unused, .{ | |
| 272 | .node = symbol.node, | |
| 273 | .kind = switch (symbol.data) { | |
| 274 | .declaration => .declaration, | |
| 275 | .param => .param, | |
| 276 | .mutable => .variable, | |
| 277 | .immutable => switch (t.ast.nodeTag(symbol.node)) { | |
| 278 | .param_decl => .param, | |
| 279 | else => .constant, | |
| 280 | }, | |
| 281 | }, | |
| 282 | }); | |
| 283 | } else if (symbol.discarded) |discard| { | |
| 284 | try t.report.warn(.unnecessary_discard, .{ | |
| 285 | .node = discard, | |
| 286 | }); | |
| 287 | } | |
| 288 | } | |
| 289 | }; | |
| 241 | symbols: std.StringArrayHashMapUnmanaged(Symbol) = .empty, | |
| 290 | 242 | |
| 291 | 243 | fn addWarnings(s: Scope, t: *Transform) !void { |
| 292 | 244 | for (s.symbols.values()) |value| { |
| ... | ... | @@ -305,9 +257,109 @@ const Scope = struct { |
| 305 | 257 | } |
| 306 | 258 | }; |
| 307 | 259 | |
| 308 | const Liveness = enum { alive, dead }; | |
| 260 | /// Sema requires that function expressions must know their up-front captures, | |
| 261 | /// so it can classify a function as comptime or runtime. | |
| 262 | const CaptureScope = struct { | |
| 263 | start_index: Teg.Inst.Index, | |
| 264 | symbol_uses: std.AutoArrayHashMapUnmanaged(Teg.Inst.Index, void) = .{}, | |
| 265 | ||
| 266 | fn deinit(s: *CaptureScope, t: *Transform) void { | |
| 267 | s.symbol_uses.deinit(t.gpa); | |
| 268 | s.* = undefined; | |
| 269 | } | |
| 270 | }; | |
| 271 | ||
| 272 | /// Identifier nodes reference a Symbol entry through the Transform's symbol | |
| 273 | /// table. The most important part of this is being able to convert it into the | |
| 274 | /// corresponding Ref or load_decl instruction when an identifier is visited. | |
| 275 | /// Additionally, there is bookkeeping information for warnings like "variable | |
| 276 | /// not mutated", which is reported just before a scope is deleted. | |
| 277 | const Symbol = struct { | |
| 278 | kind: Kind, | |
| 279 | flags: Flags, | |
| 280 | src_node: NodeIndex, | |
| 281 | value: union { | |
| 282 | mutable: Inst.Index, | |
| 283 | immutable: Inst.Ref, | |
| 284 | none: void, | |
| 285 | }, | |
| 286 | result_type_ref: Inst.Ref, | |
| 287 | discarded: OptionalNodeIndex, | |
| 288 | ||
| 289 | const Flags = packed struct { | |
| 290 | result_type_kind: enum(u1) { present, missing }, | |
| 291 | used: bool, | |
| 292 | mutated: bool, | |
| 293 | }; | |
| 294 | ||
| 295 | const Kind = enum(u8) { | |
| 296 | /// Named declaration like `let`, `fn`, `struct`, `module` | |
| 297 | declaration, | |
| 298 | /// Turns into a .variable_declaration | |
| 299 | variable_declaration, | |
| 300 | /// Function parameter | |
| 301 | param, | |
| 302 | /// Mutable local variable | |
| 303 | mutable, | |
| 304 | /// Immutable local variable | |
| 305 | immutable, | |
| 306 | }; | |
| 307 | ||
| 308 | fn init(kind: Kind) Symbol { | |
| 309 | return .{ .kind = kind }; | |
| 310 | } | |
| 311 | ||
| 312 | fn resultType(symbol: *const Symbol) ResultType { | |
| 313 | return switch (symbol.flags.result_type_kind) { | |
| 314 | .present => .{ .ref = symbol.result_type_ref }, | |
| 315 | .missing => .{ .type_of = symbol.result_type_ref }, | |
| 316 | }; | |
| 317 | } | |
| 318 | ||
| 319 | const SymbolResultType = union(enum) { | |
| 320 | /// Points to the resolved result type (.ref), as is. | |
| 321 | present: Inst.Ref, | |
| 322 | /// This points to the value. Which means a `type_of` instruction | |
| 323 | /// must be inserted before it may be used. | |
| 324 | missing: Inst.Ref, | |
| 325 | ||
| 326 | pub fn resolve(srt: SymbolResultType) ResultType { | |
| 327 | return switch (srt) { | |
| 328 | .present => |type_ref| .{ .ref = type_ref }, | |
| 329 | .missing => |value_ref| .{ .type_of = value_ref }, | |
| 330 | }; | |
| 331 | } | |
| 332 | }; | |
| 333 | ||
| 334 | pub fn addWarnings(symbol: Symbol, t: *Transform) !void { | |
| 335 | if (!symbol.flags.mutated and (symbol.kind == .mutable or symbol.kind == .variable_declaration)) { | |
| 336 | try t.report.warn(.not_mutated_var, .{ | |
| 337 | .node = symbol.src_node, | |
| 338 | }); | |
| 339 | } | |
| 309 | 340 | |
| 310 | const BuiltInIds = std.StaticStringMap(Inst.Ref).initComptime(.{ | |
| 341 | if (!symbol.flags.used) { | |
| 342 | try t.report.warn(.unused, .{ | |
| 343 | .node = symbol.src_node, | |
| 344 | .kind = switch (symbol.kind) { | |
| 345 | .declaration => .declaration, | |
| 346 | .param => .param, | |
| 347 | .mutable, .variable_declaration => .variable, | |
| 348 | .immutable => switch (t.ast.nodeTag(symbol.src_node)) { | |
| 349 | .param_decl => .param, | |
| 350 | else => .constant, | |
| 351 | }, | |
| 352 | }, | |
| 353 | }); | |
| 354 | } else if (symbol.discarded.unwrap()) |discard| { | |
| 355 | try t.report.warn(.unnecessary_discard, .{ | |
| 356 | .node = discard, | |
| 357 | }); | |
| 358 | } | |
| 359 | } | |
| 360 | }; | |
| 361 | ||
| 362 | const built_in_ids = std.StaticStringMap(Inst.Ref).initComptime(.{ | |
| 311 | 363 | .{ "void", .void }, |
| 312 | 364 | .{ "true", .bool_true }, |
| 313 | 365 | .{ "false", .bool_false }, |
| ... | ... | @@ -316,7 +368,7 @@ const BuiltInIds = std.StaticStringMap(Inst.Ref).initComptime(.{ |
| 316 | 368 | .{ "string", .string_type }, |
| 317 | 369 | // .{ "bigint", .bigint_type }, |
| 318 | 370 | .{ "bool", .bool_type }, |
| 319 | .{ "noreturn", .noreturn_type }, | |
| 371 | .{ "never", .never_type }, | |
| 320 | 372 | .{ "type", .type_type }, |
| 321 | 373 | // .{ "std", .standard_library }, |
| 322 | 374 | }); |
| ... | ... | @@ -327,261 +379,235 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I |
| 327 | 379 | |
| 328 | 380 | const module = try t.addUntracked(.module, undefined); |
| 329 | 381 | |
| 330 | const decl = BlockContext.init(module_node); | |
| 382 | const decl: BlockContext = .init(module_node); | |
| 331 | 383 | |
| 332 | 384 | try t.pushScope(); |
| 333 | 385 | defer t.popScope(); |
| 334 | 386 | |
| 387 | // Pass 1 is a light visit over every statement in order to extract | |
| 388 | // declaration names. This "hoists" the names so that declarations can be | |
| 389 | // referenced before their definition appears in code. | |
| 390 | var previsit_state: PrevisitModuleState = .{ .test_decls = .empty }; | |
| 391 | defer previsit_state.test_decls.deinit(t.gpa); | |
| 335 | 392 | const statements = t.ast.nodeStatements(module_node); |
| 336 | 393 | for (statements) |statement| { |
| 337 | try t.previsitModuleStatement(statement); | |
| 394 | try t.previsitModuleStatement(&previsit_state, statement); | |
| 338 | 395 | } |
| 339 | 396 | |
| 397 | // Pass 2 processes all declarations in order, recursively | |
| 398 | // visiting every Ast node. | |
| 340 | 399 | const s = t.scope(); |
| 341 | ||
| 342 | const temp = try t.gpa.alloc(Inst.Index, s.symbols.count() + 1); | |
| 343 | defer t.gpa.free(temp); | |
| 400 | const decl_len = s.symbols.count() + previsit_state.test_decls.items.len; | |
| 401 | const decls = try t.gpa.alloc(Inst.Index, decl_len); | |
| 402 | defer t.gpa.free(decls); | |
| 344 | 403 | |
| 345 | 404 | { |
| 346 | // prevent concurrent access | |
| 405 | // `transformDecl` is not allowed to mutate symbols as we rely on the stable value. | |
| 347 | 406 | s.symbols.lockPointers(); |
| 348 | 407 | defer s.symbols.unlockPointers(); |
| 408 | ||
| 349 | 409 | for (s.symbols.values(), 0..) |value, i| { |
| 350 | temp[i] = try t.transformDecl(decl, value.node); | |
| 410 | decls[i] = try t.transformDecl(decl, value.src_node); | |
| 351 | 411 | } |
| 352 | 412 | } |
| 353 | 413 | |
| 354 | const top_level_fn = try t.addUntracked(.declaration, undefined); | |
| 355 | ||
| 356 | temp[temp.len - 1] = top_level_fn; | |
| 414 | for (previsit_state.test_decls.items, s.symbols.count()..) |test_decl_node, i| { | |
| 415 | decls[i] = try t.transformTestDecl(decl, test_decl_node); | |
| 416 | } | |
| 357 | 417 | |
| 358 | 418 | const declarations: IndexList = .{ |
| 359 | 419 | .start = @intCast(t.extra.list.items.len), |
| 360 | .len = @intCast(temp.len), | |
| 420 | .len = @intCast(decls.len), | |
| 361 | 421 | }; |
| 362 | try t.extra.list.appendSlice(t.gpa, @ptrCast(temp)); | |
| 363 | ||
| 364 | const top_level_code_ref: Inst.Ref = ref: { | |
| 365 | const scratch = t.scratch.mark(); | |
| 366 | defer scratch.deinit(); | |
| 367 | ||
| 368 | for (statements) |statement| { | |
| 369 | try t.transformModuleStatement(decl, statement); | |
| 370 | } | |
| 371 | ||
| 372 | // if there are no new instructions, we dont have to create a block context | |
| 373 | if (scratch.len() == 0) { | |
| 374 | break :ref .void; | |
| 375 | } | |
| 376 | ||
| 377 | const block = try t.addUntracked(.block, undefined); | |
| 422 | try t.extra.list.appendSlice(t.gpa, @ptrCast(decls)); | |
| 378 | 423 | |
| 379 | t.getInstData(block).* = EncodedData.encode(.block, .{ | |
| 380 | .src_node = decl.nodeOffset(module_node), | |
| 381 | .extra_index = try t.extra.append(t.gpa, IndexList, try t.scratchToIndexList(scratch)), | |
| 382 | }); | |
| 383 | ||
| 384 | break :ref block.toRef(); | |
| 385 | }; | |
| 386 | ||
| 387 | const decl_extra, var trail = try t.extra.appendTrailing(t.gpa, Teg.Decl, .{ | |
| 388 | .kind = .function, | |
| 389 | .name = NullTerminatedString.empty, | |
| 390 | .hash = .{0} ** 16, | |
| 391 | .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(top_level_fn), | |
| 392 | .body = top_level_code_ref, | |
| 393 | }, extra.extraSizeOf(Teg.DeclFn)); | |
| 394 | ||
| 395 | trail.writeStruct(Teg.DeclFn, .{ | |
| 396 | .params = .{ .start = 0, .len = 0 }, | |
| 397 | .return_type = Inst.Ref.void.toOptional(), | |
| 398 | }); | |
| 399 | ||
| 400 | t.getInstData(top_level_fn).* = EncodedData.encode(.declaration, .{ | |
| 401 | .src_node = decl.nodeOffset(module_node), | |
| 402 | .extra_index = decl_extra, | |
| 403 | }); | |
| 404 | ||
| 405 | t.getInstData(module).* = EncodedData.encode(.module, .{ | |
| 424 | t.getInstData(module).* = .encode(.module, .{ | |
| 406 | 425 | .src_node = 0, |
| 407 | 426 | .extra_index = try t.extra.append(t.gpa, IndexList, declarations), |
| 408 | 427 | }); |
| 409 | 428 | |
| 429 | // `fn main` is a special function as it does not have to be public | |
| 430 | // in order for the compiler to use it as the entry point. | |
| 431 | if (module_node == 0) { | |
| 432 | if (t.scope().symbols.getPtr("main")) |main| { | |
| 433 | main.flags.used = true; | |
| 434 | } | |
| 435 | } | |
| 436 | ||
| 410 | 437 | try t.scope().addWarnings(t); |
| 411 | 438 | |
| 412 | 439 | return module; |
| 413 | 440 | } |
| 414 | 441 | |
| 415 | fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void { | |
| 442 | const PrevisitModuleState = struct { | |
| 443 | test_decls: std.ArrayListUnmanaged(NodeIndex), | |
| 444 | }; | |
| 445 | ||
| 446 | fn previsitModuleStatement(t: *Transform, state: *PrevisitModuleState, node: NodeIndex) Allocator.Error!void { | |
| 416 | 447 | const trace = tracer.trace(@src()); |
| 417 | 448 | defer trace.end(); |
| 418 | 449 | |
| 419 | const tag = t.ast.nodes.items(.tag)[node]; | |
| 420 | switch (tag) { | |
| 421 | .fn_decl => { | |
| 422 | const decl = t.ast.fullFnDecl(node); | |
| 423 | var id, const id_allocated = try t.decodeIdentifierExtra(decl.id); | |
| 450 | switch (t.ast.nodes.items(.tag)[node]) { | |
| 451 | // The paths of these three are all very similar and | |
| 452 | // it is worth grouping them together | |
| 453 | inline .fn_decl, | |
| 454 | .struct_decl, | |
| 455 | .var_decl, | |
| 456 | => |tag| { | |
| 457 | const data = switch (tag) { | |
| 458 | .var_decl => t.ast.fullVarDecl(node), | |
| 459 | .fn_decl => t.ast.fullFnDecl(node), | |
| 460 | .struct_decl => t.ast.fullStructDecl(node), | |
| 461 | else => @compileError(unreachable), | |
| 462 | }; | |
| 463 | ||
| 464 | const new_kind: Symbol.Kind = if (tag == .var_decl and !data.is_const) | |
| 465 | .variable_declaration | |
| 466 | else | |
| 467 | .declaration; | |
| 468 | ||
| 469 | var id, const id_allocated = try t.decodeIdentifierExtra(data.id); | |
| 424 | 470 | if (id_allocated) |
| 425 | 471 | id = try t.gpa.dupe(u8, id); |
| 472 | ||
| 426 | 473 | const gop = try t.scope().symbols.getOrPut(t.gpa, id); |
| 427 | 474 | if (gop.found_existing) { |
| 428 | 475 | if (id_allocated) t.gpa.free(id); |
| 476 | ||
| 429 | 477 | try t.report.err(.duplicate_function, .{ |
| 430 | 478 | .node = node, |
| 431 | .prev = gop.value_ptr.node, | |
| 479 | .prev = gop.value_ptr.src_node, | |
| 432 | 480 | }); |
| 433 | 481 | return; |
| 434 | 482 | } |
| 483 | ||
| 435 | 484 | gop.value_ptr.* = .{ |
| 436 | .node = node, | |
| 437 | .data = .declaration, | |
| 438 | .type = .{ .present = .void }, | |
| 485 | .kind = new_kind, | |
| 486 | .flags = .{ | |
| 487 | .result_type_kind = undefined, | |
| 488 | .used = false, | |
| 489 | .mutated = false, | |
| 490 | }, | |
| 491 | .src_node = node, | |
| 492 | .discarded = .none, | |
| 493 | .result_type_ref = undefined, | |
| 494 | .value = undefined, | |
| 439 | 495 | }; |
| 440 | 496 | }, |
| 441 | .struct_decl => { | |
| 442 | const decl = t.ast.fullStructDecl(node); | |
| 443 | var id, const id_allocated = try t.decodeIdentifierExtra(decl.id); | |
| 444 | if (id_allocated) | |
| 445 | id = try t.gpa.dupe(u8, id); | |
| 446 | const gop = try t.scope().symbols.getOrPut(t.gpa, decl.id); | |
| 447 | if (gop.found_existing) { | |
| 448 | try t.report.err(.duplicate_function, .{ | |
| 449 | .node = node, | |
| 450 | .prev = gop.value_ptr.node, | |
| 451 | // TODO: put type of both declarations | |
| 452 | }); | |
| 453 | return; | |
| 454 | } | |
| 455 | gop.value_ptr.* = .{ | |
| 456 | .node = node, | |
| 457 | .data = .declaration, | |
| 458 | .type = .{ .present = .void }, | |
| 497 | .test_decl => { | |
| 498 | try state.test_decls.append(t.arena, node); | |
| 499 | }, | |
| 500 | else => if (!t.has_warned_top_level_statement) { | |
| 501 | t.has_warned_top_level_statement = true; | |
| 502 | try t.report.err(.top_level_statement, .{ .src = node }); | |
| 503 | ||
| 504 | // Visit the statement so that any identifiers get tracked, so that mistakes in the code get caught | |
| 505 | const insts_top = t.insts.len; | |
| 506 | defer t.insts.len = insts_top; | |
| 507 | const extra_top = t.extra.list.items.len; | |
| 508 | defer t.extra.list.items.len = extra_top; | |
| 509 | const string_top = t.string_bytes.items.len; | |
| 510 | defer t.string_bytes.items.len = string_top; | |
| 511 | ||
| 512 | _ = transformBlockStatement(t, .init(node), node, null) catch |err| switch (err) { | |
| 513 | error.OutOfMemory => return err, | |
| 459 | 514 | }; |
| 460 | 515 | }, |
| 461 | else => {}, | |
| 462 | } | |
| 463 | } | |
| 464 | ||
| 465 | fn transformModuleStatement(t: *Transform, decl: BlockContext, node: NodeIndex) !void { | |
| 466 | const trace = tracer.trace(@src()); | |
| 467 | defer trace.end(); | |
| 468 | ||
| 469 | const tag = t.ast.nodes.items(.tag)[node]; | |
| 470 | switch (tag) { | |
| 471 | .root, | |
| 472 | .param_decl, | |
| 473 | => unreachable, | |
| 474 | ||
| 475 | .fn_decl, | |
| 476 | .struct_decl, | |
| 477 | => {}, // declarations are analyzed separately | |
| 478 | ||
| 479 | else => _ = try t.transformBlockStatement(decl, node, null), | |
| 480 | 516 | } |
| 481 | 517 | } |
| 482 | 518 | |
| 483 | 519 | fn transformDecl(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index { |
| 484 | return switch (t.ast.nodeTag(node)) { | |
| 485 | .fn_decl => t.transformFunction(outer_decl, node), | |
| 486 | .struct_decl => t.transformStructDeclaration(outer_decl, node), | |
| 487 | else => unreachable, // not a declaration | |
| 488 | }; | |
| 489 | } | |
| 490 | ||
| 491 | fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index { | |
| 492 | 520 | const trace = tracer.trace(@src()); |
| 493 | 521 | defer trace.end(); |
| 494 | 522 | |
| 495 | const decl = outer_decl.declOffset(node); | |
| 496 | const fn_decl = t.ast.fullFnDecl(node); | |
| 523 | const decl_index = try t.addUntracked(.declaration, undefined); | |
| 497 | 524 | |
| 498 | const decl_inst = try t.addUntracked(.declaration, undefined); | |
| 525 | const inner_ctx = outer_decl.declOffset(node); | |
| 526 | const body = try t.transformToBlock(inner_ctx, node, .untracked_result, .none, .run_time); | |
| 499 | 527 | |
| 500 | try t.pushScope(); | |
| 501 | defer t.popScope(); | |
| 528 | const inst_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_index); | |
| 502 | 529 | |
| 503 | const param_list = brk: { | |
| 504 | const scratch = t.scratch.mark(); | |
| 505 | defer scratch.deinit(); | |
| 530 | const id_encoded = t.ast.tokenSlice(t.ast.idToken(node)); | |
| 531 | const id = try t.decodeIdentifier(id_encoded); | |
| 532 | const name = try t.addNullTerminatedString(id); | |
| 506 | 533 | |
| 507 | for (fn_decl.params) |param| { | |
| 508 | try t.scratch.add(t.arena, Inst.Ref, try t.transformParamDecl(decl, param)); | |
| 509 | } | |
| 534 | var hasher: std.Build.Cache.HashHelper = .{ .hasher = core.Cache.hasher_init }; | |
| 535 | hasher.addBytes(id); | |
| 510 | 536 | |
| 511 | break :brk try t.scratchToRefList(scratch); | |
| 537 | const temp_teg: Teg = .{ | |
| 538 | .insts = t.insts.slice(), | |
| 539 | .extra = t.extra.constant(), | |
| 540 | .string_bytes = t.string_bytes.items, | |
| 541 | .report = undefined, | |
| 512 | 542 | }; |
| 513 | 543 | |
| 514 | const return_type = if (fn_decl.return_type) |return_type_node| | |
| 515 | try t.transformToBlock( | |
| 516 | decl, | |
| 517 | return_type_node, | |
| 518 | .result_without_coercion, | |
| 519 | .{ .ref = .type_type }, | |
| 520 | ) | |
| 521 | else | |
| 522 | null; | |
| 523 | ||
| 524 | const body = try t.transformToBlock( | |
| 525 | decl, | |
| 526 | fn_decl.body, | |
| 527 | .function_body, | |
| 528 | if (return_type) |ref| .{ .ref = ref } else .none, | |
| 529 | ); | |
| 544 | const tag_list = temp_teg.insts.items(.tag)[@intFromEnum(decl_index) + 1 ..]; | |
| 545 | const data_list = temp_teg.insts.items(.data)[@intFromEnum(decl_index) + 1 ..]; | |
| 546 | ||
| 547 | temp_teg.writeToHasher(&hasher, tag_list, data_list, decl_index); | |
| 530 | 548 | |
| 531 | 549 | const decl_extra = try t.extra.append(t.gpa, Teg.Decl, .{ |
| 532 | .kind = .function, | |
| 533 | .name = try t.addNullTerminatedString(fn_decl.id), | |
| 534 | .hash = .{0} ** 16, | |
| 535 | .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_inst), | |
| 550 | .name = name, | |
| 551 | .hash = hasher.hasher.finalResult(), | |
| 552 | .instruction_count = inst_count, | |
| 536 | 553 | .body = body, |
| 537 | 554 | }); |
| 538 | 555 | |
| 539 | _ = try t.extra.append(t.gpa, Teg.DeclFn, .{ | |
| 540 | .params = param_list, | |
| 541 | .return_type = if (return_type) |r| r.toOptional() else .none, | |
| 542 | }); | |
| 543 | ||
| 544 | t.getInstData(decl_inst).* = EncodedData.encode(.declaration, .{ | |
| 556 | t.getInstData(decl_index).* = .encode(.declaration, .{ | |
| 545 | 557 | .src_node = outer_decl.nodeOffset(node), |
| 546 | 558 | .extra_index = decl_extra, |
| 547 | 559 | }); |
| 548 | 560 | |
| 549 | try t.scope().addWarnings(t); | |
| 550 | ||
| 551 | return decl_inst; | |
| 561 | return decl_index; | |
| 552 | 562 | } |
| 553 | 563 | |
| 554 | fn transformStructDeclaration(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index { | |
| 564 | fn transformTestDecl(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index { | |
| 555 | 565 | const trace = tracer.trace(@src()); |
| 556 | 566 | defer trace.end(); |
| 557 | 567 | |
| 558 | const decl = outer_decl.declOffset(node); | |
| 559 | const decl_data = t.ast.fullStructDecl(node); | |
| 568 | const decl_index = try t.addUntracked(.test_declaration, undefined); | |
| 569 | ||
| 570 | const test_decl = t.ast.nodeData(node).test_decl; | |
| 571 | if (test_decl.doc_test_name != .none) @panic("TODO"); | |
| 572 | const body_node = test_decl.block; | |
| 573 | ||
| 574 | const inner_ctx = outer_decl.declOffset(node); | |
| 575 | const body = try t.transformToBlock(inner_ctx, body_node, .untracked_result, .none, .run_time); | |
| 576 | ||
| 577 | const inst_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_index); | |
| 578 | ||
| 579 | var hasher: std.Build.Cache.HashHelper = .{ .hasher = core.Cache.hasher_init }; | |
| 580 | ||
| 581 | const temp_teg: Teg = .{ | |
| 582 | .insts = t.insts.slice(), | |
| 583 | .extra = t.extra.constant(), | |
| 584 | .string_bytes = t.string_bytes.items, | |
| 585 | .report = undefined, | |
| 586 | }; | |
| 560 | 587 | |
| 561 | const decl_inst = try t.addUntracked(.declaration, undefined); | |
| 588 | const tag_list = temp_teg.insts.items(.tag)[@intFromEnum(decl_index) + 1 ..]; | |
| 589 | const data_list = temp_teg.insts.items(.data)[@intFromEnum(decl_index) + 1 ..]; | |
| 562 | 590 | |
| 563 | const body = try t.transformToBlock(decl, node, .result_without_coercion, .none); | |
| 591 | temp_teg.writeToHasher(&hasher, tag_list, data_list, decl_index); | |
| 564 | 592 | |
| 565 | 593 | const decl_extra = try t.extra.append(t.gpa, Teg.Decl, .{ |
| 566 | .kind = .immutable, | |
| 567 | .name = try t.addNullTerminatedString(decl_data.id), | |
| 568 | .hash = .{0} ** 16, | |
| 569 | .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_inst), | |
| 594 | .name = .empty, | |
| 595 | .hash = hasher.hasher.finalResult(), | |
| 596 | .instruction_count = inst_count, | |
| 570 | 597 | .body = body, |
| 571 | 598 | }); |
| 572 | 599 | |
| 573 | t.getInstData(decl_inst).* = EncodedData.encode(.declaration, .{ | |
| 600 | t.getInstData(decl_index).* = .encode(.test_declaration, .{ | |
| 574 | 601 | .src_node = outer_decl.nodeOffset(node), |
| 575 | 602 | .extra_index = decl_extra, |
| 576 | 603 | }); |
| 577 | 604 | |
| 578 | return decl_inst; | |
| 605 | return decl_index; | |
| 579 | 606 | } |
| 580 | 607 | |
| 581 | 608 | const TransformToBlockMode = enum { |
| 582 | 609 | tracked_result, |
| 583 | result_without_coercion, | |
| 584 | coerced_result, | |
| 610 | untracked_result, | |
| 585 | 611 | function_body, |
| 586 | 612 | |
| 587 | 613 | fn ResultType(mode: TransformToBlockMode) type { |
| ... | ... | @@ -590,42 +616,63 @@ const TransformToBlockMode = enum { |
| 590 | 616 | else => Transform.ResultType, |
| 591 | 617 | }; |
| 592 | 618 | } |
| 619 | ||
| 620 | fn InstType(mode: TransformToBlockMode) type { | |
| 621 | return switch (mode) { | |
| 622 | .function_body => Inst.Index, | |
| 623 | else => Inst.Ref, | |
| 624 | }; | |
| 625 | } | |
| 593 | 626 | }; |
| 594 | 627 | |
| 595 | /// guh | |
| 628 | const BlockType = enum { | |
| 629 | run_time, | |
| 630 | compile_time, | |
| 631 | }; | |
| 632 | ||
| 633 | // todo: clarify | |
| 634 | /// Visit and convert an expression into either a Value with no instructions, or | |
| 635 | /// a single instruction. | |
| 636 | /// | |
| 637 | /// This is important for function bodies, loop bodies, and branches, so that | |
| 638 | /// contents are grouped into a nested block. | |
| 596 | 639 | fn transformToBlock( |
| 597 | 640 | t: *Transform, |
| 598 | 641 | ctx: BlockContext, |
| 599 | 642 | node: NodeIndex, |
| 600 | 643 | comptime mode: TransformToBlockMode, |
| 601 | 644 | result_type: mode.ResultType(), |
| 602 | ) Allocator.Error!Inst.Ref { | |
| 645 | block_type: BlockType, | |
| 646 | ) Allocator.Error!mode.InstType() { | |
| 603 | 647 | const trace = tracer.trace(@src()); |
| 604 | 648 | defer trace.end(); |
| 605 | 649 | |
| 606 | 650 | const scratch = t.scratch.mark(); |
| 607 | 651 | defer scratch.deinit(); |
| 608 | 652 | |
| 653 | // The result type is only passed by reference if mode is tracked_result. | |
| 654 | const copies_result_type = mode.ResultType() == ResultType; | |
| 655 | var result_type_storage = if (copies_result_type) result_type; | |
| 656 | ||
| 657 | var ret = if (mode == .function_body) | |
| 658 | BlockContext.ReturnTarget{ | |
| 659 | .result_type = result_type_storage, | |
| 660 | .block_index = try t.addUntracked(.block, undefined), | |
| 661 | }; | |
| 662 | ||
| 609 | 663 | // For function blocks, add .param instructions for all of the parameters |
| 610 | 664 | if (mode == .function_body) { |
| 611 | 665 | const s = t.scopes.getLast(); |
| 612 | 666 | for (s.symbols.values(), 0..) |*param, i| { |
| 613 | assert(param.data == .param); | |
| 667 | assert(param.kind == .param); | |
| 614 | 668 | const ref = try t.addRef(.param, .{ |
| 615 | 669 | .src_node = ctx.nodeOffset(node), |
| 616 | 670 | .data = @intCast(i), |
| 617 | 671 | }); |
| 618 | param.data = .{ .immutable = ref }; | |
| 672 | param.value = .{ .immutable = ref }; | |
| 619 | 673 | } |
| 620 | 674 | } |
| 621 | 675 | |
| 622 | // The result type is only passed by reference if mode is tracked_result. | |
| 623 | const copies_result_type = mode.ResultType() == ResultType; | |
| 624 | var result_type_storage = if (copies_result_type) result_type; | |
| 625 | ||
| 626 | var ret = if (mode == .function_body) | |
| 627 | BlockContext.ReturnTarget{ .result_type = result_type_storage }; | |
| 628 | ||
| 629 | 676 | const expr = try t.transformExpression( |
| 630 | 677 | // Function blocks are allowed to return, in which |
| 631 | 678 | // it will return to this block. |
| ... | ... | @@ -641,15 +688,15 @@ fn transformToBlock( |
| 641 | 688 | node, |
| 642 | 689 | ); |
| 643 | 690 | |
| 644 | // if there are no new instructions, we dont have to create a block context | |
| 645 | if (scratch.len() == 0) { | |
| 646 | const result = expr orelse | |
| 647 | unreachable; // null means we *added* break or return | |
| 648 | ||
| 649 | if (result.value() != null) { | |
| 650 | if (mode == .function_body) | |
| 651 | assert(ret.generated_block == null); // return was called without an instruction | |
| 691 | // if we were given a value without any new instructions, we can just | |
| 692 | // forward that value. | |
| 693 | if (mode != .function_body and scratch.len() == 0) { | |
| 694 | const result = expr orelse { | |
| 695 | assert(t.report.errors.items.len > 0); | |
| 696 | return .void; // null means control flow ended, and with no instructions guarantees a compile error | |
| 697 | }; | |
| 652 | 698 | |
| 699 | if (result.decode() == .value) { | |
| 653 | 700 | return result; |
| 654 | 701 | } |
| 655 | 702 | } |
| ... | ... | @@ -658,14 +705,20 @@ fn transformToBlock( |
| 658 | 705 | if (expr) |result| { |
| 659 | 706 | if (result.index()) |index| { |
| 660 | 707 | if (t.insts.items(.tag)[@intFromEnum(index)] == .block) { |
| 708 | if (mode == .function_body) | |
| 709 | return index; | |
| 661 | 710 | return result; |
| 662 | 711 | } |
| 663 | 712 | } |
| 664 | 713 | } |
| 665 | 714 | |
| 666 | 715 | // Otherwise try using the function return block, or create a new one |
| 667 | const maybe_generated_block = if (mode == .function_body) ret.generated_block else null; | |
| 668 | const ret_block = maybe_generated_block orelse try t.addUntracked(.block, undefined); | |
| 716 | const ret_block = if (mode == .function_body) | |
| 717 | ret.block_index | |
| 718 | else switch (block_type) { | |
| 719 | .run_time => try t.addUntracked(.block, undefined), | |
| 720 | .compile_time => try t.addUntracked(.@"comptime", undefined), | |
| 721 | }; | |
| 669 | 722 | |
| 670 | 723 | // The block must end with a break |
| 671 | 724 | if (expr) |result| { |
| ... | ... | @@ -681,12 +734,12 @@ fn transformToBlock( |
| 681 | 734 | const content = try t.scratchToIndexList(scratch); |
| 682 | 735 | assert(content.len > 0); |
| 683 | 736 | |
| 684 | t.getInstData(ret_block).* = EncodedData.encode(.block, .{ | |
| 737 | t.getInstData(ret_block).* = .encode(.block, .{ | |
| 685 | 738 | .src_node = ctx.nodeOffset(node), |
| 686 | 739 | .extra_index = try t.extra.append(t.gpa, IndexList, content), |
| 687 | 740 | }); |
| 688 | 741 | |
| 689 | return ret_block.toRef(); | |
| 742 | return if (mode == .function_body) ret_block else ret_block.toRef(); | |
| 690 | 743 | } |
| 691 | 744 | |
| 692 | 745 | fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Allocator.Error!Inst.Ref { |
| ... | ... | @@ -698,8 +751,9 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc |
| 698 | 751 | decl, |
| 699 | 752 | param_decl.type orelse |
| 700 | 753 | @panic("TODO: inferred param types"), |
| 701 | .result_without_coercion, | |
| 754 | .untracked_result, | |
| 702 | 755 | .{ .ref = .type_type }, |
| 756 | .compile_time, | |
| 703 | 757 | ); |
| 704 | 758 | |
| 705 | 759 | var id, const id_allocated = try t.decodeIdentifierExtra(param_decl.id); |
| ... | ... | @@ -713,22 +767,29 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc |
| 713 | 767 | if (id_allocated) t.gpa.free(id); |
| 714 | 768 | try t.report.err(.duplicate_param, .{ |
| 715 | 769 | .node = param, |
| 716 | .prev = sym.value_ptr.node, | |
| 770 | .prev = sym.value_ptr.src_node, | |
| 717 | 771 | }); |
| 718 | 772 | return .void; |
| 719 | 773 | } |
| 720 | 774 | |
| 721 | 775 | sym.value_ptr.* = .{ |
| 722 | .node = param, | |
| 723 | .data = .param, | |
| 724 | .type = .{ .present = ty }, | |
| 776 | .src_node = param, | |
| 777 | .kind = .param, | |
| 778 | .flags = .{ | |
| 779 | .result_type_kind = .present, | |
| 780 | .used = false, | |
| 781 | .mutated = false, | |
| 782 | }, | |
| 783 | .value = .{ .none = {} }, | |
| 784 | .result_type_ref = ty, | |
| 785 | .discarded = .none, | |
| 725 | 786 | }; |
| 726 | 787 | |
| 727 | 788 | return ty; |
| 728 | 789 | } |
| 729 | 790 | |
| 730 | 791 | pub const Builtin = enum { |
| 731 | js, | |
| 792 | @"extern", | |
| 732 | 793 | js_new, |
| 733 | 794 | import, |
| 734 | 795 | fetch, |
| ... | ... | @@ -740,13 +801,13 @@ pub const Builtin = enum { |
| 740 | 801 | .{ "compileError", .compile_error }, |
| 741 | 802 | .{ "fetch", .fetch }, |
| 742 | 803 | .{ "import", .import }, |
| 743 | .{ "js", .js }, | |
| 804 | .{ "extern", .@"extern" }, | |
| 744 | 805 | .{ "jsNew", .js_new }, |
| 745 | 806 | .{ "typeOf", .type_of }, |
| 746 | 807 | // sort-lines: end |
| 747 | 808 | }); |
| 748 | 809 | |
| 749 | fn builtin_js(t: *Transform, decl: BlockContext, node: NodeIndex, args: []const NodeIndex) !?Inst.Ref { | |
| 810 | fn builtin_extern(t: *Transform, decl: BlockContext, node: NodeIndex, args: []const NodeIndex) !?Inst.Ref { | |
| 750 | 811 | const trace = tracer.trace(@src()); |
| 751 | 812 | defer trace.end(); |
| 752 | 813 | |
| ... | ... | @@ -758,7 +819,7 @@ pub const Builtin = enum { |
| 758 | 819 | var result: ResultType = .{ .ref = .string_type }; |
| 759 | 820 | const source = try t.transformExpression(decl, &result, args[0]) orelse return null; |
| 760 | 821 | |
| 761 | return try t.addRef(.js, .{ | |
| 822 | return try t.addRef(.@"extern", .{ | |
| 762 | 823 | .src_node = decl.nodeOffset(args[0]), |
| 763 | 824 | .operand = source, |
| 764 | 825 | }); |
| ... | ... | @@ -841,7 +902,6 @@ fn transformExpression( |
| 841 | 902 | switch (node_tag) { |
| 842 | 903 | .root, |
| 843 | 904 | .param_decl, |
| 844 | .var_decl, | |
| 845 | 905 | => unreachable, // not an expression |
| 846 | 906 | |
| 847 | 907 | .id => { |
| ... | ... | @@ -851,7 +911,7 @@ fn transformExpression( |
| 851 | 911 | return .void; |
| 852 | 912 | } |
| 853 | 913 | |
| 854 | if (BuiltInIds.get(id)) |ref| { | |
| 914 | if (built_in_ids.get(id)) |ref| { | |
| 855 | 915 | return ref; |
| 856 | 916 | } |
| 857 | 917 | |
| ... | ... | @@ -860,17 +920,53 @@ fn transformExpression( |
| 860 | 920 | return .void; |
| 861 | 921 | }; |
| 862 | 922 | |
| 863 | lookup.used = true; | |
| 923 | lookup.flags.used = true; | |
| 864 | 924 | |
| 865 | return switch (lookup.data) { | |
| 866 | .declaration => try t.addRef(.load_decl, .{ | |
| 925 | switch (lookup.kind) { | |
| 926 | .declaration, | |
| 927 | .variable_declaration, | |
| 928 | => return try t.addRef(.load_decl, .{ | |
| 867 | 929 | .src_node = ctx.nodeOffset(node), |
| 868 | 930 | .string = try t.addNullTerminatedString(id), |
| 869 | 931 | }), |
| 870 | .mutable => |idx| idx.toRef(), | |
| 871 | .immutable => |ref| ref, | |
| 872 | .param => unreachable, // unresolved param | |
| 873 | }; | |
| 932 | .mutable => { | |
| 933 | const ref = lookup.value.mutable.toRef(); | |
| 934 | try t.markCapturedRef(ref); | |
| 935 | return ref; | |
| 936 | }, | |
| 937 | .param, .immutable => { | |
| 938 | const ref = lookup.value.immutable; | |
| 939 | try t.markCapturedRef(ref); | |
| 940 | return ref; | |
| 941 | }, | |
| 942 | } | |
| 943 | }, | |
| 944 | ||
| 945 | // var_decl *expression* is special cased for decl contents. | |
| 946 | // instead of defining a variable, it will just perform the coercion. | |
| 947 | .var_decl => { | |
| 948 | const decl = t.ast.fullVarDecl(node); | |
| 949 | var type_result: ResultType = .{ .ref = .type_type }; | |
| 950 | const type_ref = if (decl.type) |ty| | |
| 951 | try t.transformExpression(ctx, &type_result, ty) orelse | |
| 952 | return null | |
| 953 | else | |
| 954 | null; | |
| 955 | ||
| 956 | var value_result: ResultType = if (type_ref) |ty| .{ .ref = ty } else .none; | |
| 957 | const data_ref = try t.transformExpression(ctx, &value_result, decl.init) orelse | |
| 958 | return null; | |
| 959 | ||
| 960 | if (value_result == .used or type_ref == null) | |
| 961 | return data_ref; | |
| 962 | ||
| 963 | return try t.addRef(.coercion, .{ | |
| 964 | .src_node = ctx.nodeOffset(node), | |
| 965 | .extra_index = try t.extra.append(t.gpa, Teg.Coercion, .{ | |
| 966 | .type = type_ref.?, | |
| 967 | .operand = data_ref, | |
| 968 | }), | |
| 969 | }); | |
| 874 | 970 | }, |
| 875 | 971 | |
| 876 | 972 | inline .number_int, .number_float => |tag| { |
| ... | ... | @@ -910,18 +1006,11 @@ fn transformExpression( |
| 910 | 1006 | |
| 911 | 1007 | .assign => { |
| 912 | 1008 | const ast_bin = t.ast.nodeData(node).bin; |
| 913 | const lhs, var result_type = try t.transformAssignmentTarget(ctx, ast_bin.lhs) orelse | |
| 1009 | const target, var result_type = try t.transformAssignmentTarget(ctx, ast_bin.lhs) orelse | |
| 914 | 1010 | return .void; |
| 915 | 1011 | const rhs = try t.transformExpression(ctx, &result_type, ast_bin.rhs) orelse |
| 916 | 1012 | return null; |
| 917 | const assign_inst = try t.addRef(.store, .{ | |
| 918 | .src_node = ctx.nodeOffset(node), | |
| 919 | .extra_index = try t.extra.append(t.gpa, Teg.Store, .{ | |
| 920 | .memory = lhs, | |
| 921 | .operand = rhs, | |
| 922 | }), | |
| 923 | }); | |
| 924 | return assign_inst; | |
| 1013 | return try target.emit(t, rhs, ctx, node); | |
| 925 | 1014 | }, |
| 926 | 1015 | |
| 927 | 1016 | inline .add, |
| ... | ... | @@ -983,20 +1072,13 @@ fn transformExpression( |
| 983 | 1072 | const compute_inst = try t.addRef(teg_tag, .{ |
| 984 | 1073 | .src_node = ctx.nodeOffset(node), |
| 985 | 1074 | .extra_index = try t.extra.append(t.gpa, Teg.Bin, .{ |
| 986 | .lhs = if (is_assign) lhs.toRef() else lhs, | |
| 1075 | .lhs = if (is_assign) try lhs.toRef(t) else lhs, | |
| 987 | 1076 | .rhs = rhs, |
| 988 | 1077 | }), |
| 989 | 1078 | }); |
| 990 | 1079 | |
| 991 | 1080 | if (is_assign) { |
| 992 | const assign_inst = try t.addRef(.store, .{ | |
| 993 | .src_node = ctx.nodeOffset(node), | |
| 994 | .extra_index = try t.extra.append(t.gpa, Teg.Store, .{ | |
| 995 | .memory = lhs, | |
| 996 | .operand = compute_inst, | |
| 997 | }), | |
| 998 | }); | |
| 999 | return assign_inst; | |
| 1081 | return try lhs.emit(t, compute_inst, ctx, node); | |
| 1000 | 1082 | } |
| 1001 | 1083 | |
| 1002 | 1084 | return compute_inst; |
| ... | ... | @@ -1006,6 +1088,7 @@ fn transformExpression( |
| 1006 | 1088 | .bool_not, |
| 1007 | 1089 | .bit_not, |
| 1008 | 1090 | .array_type, |
| 1091 | .assert, | |
| 1009 | 1092 | => |tag| { |
| 1010 | 1093 | const ast_un = t.ast.nodeData(node).un; |
| 1011 | 1094 | const teg_tag = @field(Teg.Tag, @tagName(tag)); |
| ... | ... | @@ -1015,6 +1098,7 @@ fn transformExpression( |
| 1015 | 1098 | .bool_not => .{ .ref = .bool_type }, |
| 1016 | 1099 | .bit_not => .{ .ref = .int32_type }, |
| 1017 | 1100 | .array_type => .{ .ref = .type_type }, |
| 1101 | .assert => .{ .ref = .bool_type }, | |
| 1018 | 1102 | else => @compileError(unreachable), |
| 1019 | 1103 | }; |
| 1020 | 1104 | |
| ... | ... | @@ -1029,11 +1113,20 @@ fn transformExpression( |
| 1029 | 1113 | |
| 1030 | 1114 | .@"comptime" => { |
| 1031 | 1115 | const ast_un = t.ast.nodeData(node).un; |
| 1032 | const operand = try t.transformToBlock(ctx, ast_un.expr, .tracked_result, result); | |
| 1033 | return try t.addRef(.@"comptime", .{ | |
| 1034 | .src_node = ctx.nodeOffset(node), | |
| 1035 | .operand = operand, | |
| 1036 | }); | |
| 1116 | const operand = try t.transformToBlock(ctx, ast_un.expr, .tracked_result, result, .compile_time); | |
| 1117 | // Move the source index to the `comptime` node | |
| 1118 | if (operand.index()) |index| { | |
| 1119 | const slice = t.insts.slice(); | |
| 1120 | const data: *EncodedData = &slice.items(.data)[@intFromEnum(index)]; | |
| 1121 | var existing = data.decode(.block, slice.items(.tag)[@intFromEnum(index)]); | |
| 1122 | existing.src_node = ctx.nodeOffset(node); | |
| 1123 | data.* = .encode(.block, existing); | |
| 1124 | ||
| 1125 | // Since the block was added in an untracked fashion, it | |
| 1126 | // needs to be tracked it before returning. | |
| 1127 | try t.scratch.add(t.arena, Inst.Index, index); | |
| 1128 | } | |
| 1129 | return operand; | |
| 1037 | 1130 | }, |
| 1038 | 1131 | |
| 1039 | 1132 | .label => { |
| ... | ... | @@ -1054,8 +1147,7 @@ fn transformExpression( |
| 1054 | 1147 | |
| 1055 | 1148 | // if there are no new instructions, we dont have to create a block context |
| 1056 | 1149 | if (scratch.len() == 0) |
| 1057 | return inner_expr orelse | |
| 1058 | unreachable; // null means we added break or return | |
| 1150 | return inner_expr; | |
| 1059 | 1151 | |
| 1060 | 1152 | if (target.block_index) |block| { |
| 1061 | 1153 | if (inner_expr) |out| { |
| ... | ... | @@ -1071,7 +1163,7 @@ fn transformExpression( |
| 1071 | 1163 | const content = try t.scratchToIndexList(scratch); |
| 1072 | 1164 | scratch.deinit(); |
| 1073 | 1165 | |
| 1074 | t.getInstData(block).* = EncodedData.encode(.block, .{ | |
| 1166 | t.getInstData(block).* = .encode(.block, .{ | |
| 1075 | 1167 | .src_node = ctx.nodeOffset(node), |
| 1076 | 1168 | .extra_index = try t.extra.append(t.gpa, IndexList, content), |
| 1077 | 1169 | }); |
| ... | ... | @@ -1080,7 +1172,7 @@ fn transformExpression( |
| 1080 | 1172 | |
| 1081 | 1173 | return block.toRef(); |
| 1082 | 1174 | } else { |
| 1083 | std.debug.print("Warn, unused label {s}", .{label}); | |
| 1175 | std.debug.print("Warn, unused label {s}\n", .{label}); | |
| 1084 | 1176 | } |
| 1085 | 1177 | |
| 1086 | 1178 | return inner_expr; |
| ... | ... | @@ -1119,7 +1211,7 @@ fn transformExpression( |
| 1119 | 1211 | const args = base.slice(&t.ast); |
| 1120 | 1212 | |
| 1121 | 1213 | return switch (builtin) { |
| 1122 | .js => Builtin.builtin_js(t, ctx, node, args), | |
| 1214 | .@"extern" => Builtin.builtin_extern(t, ctx, node, args), | |
| 1123 | 1215 | .js_new => Builtin.builtin_jsNew(t, ctx, node, args), |
| 1124 | 1216 | // .import => Builtin.import(t, decl, node, args), |
| 1125 | 1217 | // .fetch => Builtin.fetch(t, decl, node, args), |
| ... | ... | @@ -1238,17 +1330,11 @@ fn transformExpression( |
| 1238 | 1330 | return null; |
| 1239 | 1331 | |
| 1240 | 1332 | var yes_result = result.*; |
| 1241 | const yes = try t.transformToBlock(ctx, if_data.yes, .tracked_result, &yes_result); | |
| 1333 | const yes = try t.transformToBlock(ctx, if_data.yes, .tracked_result, &yes_result, .run_time); | |
| 1242 | 1334 | |
| 1243 | // TODO: can this re-use the resolved ref from above | |
| 1244 | 1335 | var no_result: ResultType = if (if_data.no == .none) .used else result.*; |
| 1245 | 1336 | const no = if (if_data.no.unwrap()) |no| |
| 1246 | try t.transformToBlock( | |
| 1247 | ctx, | |
| 1248 | no, | |
| 1249 | .tracked_result, | |
| 1250 | &no_result, | |
| 1251 | ) | |
| 1337 | try t.transformToBlock(ctx, no, .tracked_result, &no_result, .run_time) | |
| 1252 | 1338 | else |
| 1253 | 1339 | Inst.Ref.void; |
| 1254 | 1340 | |
| ... | ... | @@ -1275,14 +1361,6 @@ fn transformExpression( |
| 1275 | 1361 | return .void; |
| 1276 | 1362 | }; |
| 1277 | 1363 | |
| 1278 | const ret_block = if (target.generated_block) |block| | |
| 1279 | block | |
| 1280 | else append_block: { | |
| 1281 | const block = try t.addUntracked(.block, undefined); | |
| 1282 | target.generated_block = block; | |
| 1283 | break :append_block block; | |
| 1284 | }; | |
| 1285 | ||
| 1286 | 1364 | var return_result = target.result_type; |
| 1287 | 1365 | |
| 1288 | 1366 | const operand: Inst.Ref = if (ret_node.expr.unwrap()) |expr| |
| ... | ... | @@ -1297,7 +1375,7 @@ fn transformExpression( |
| 1297 | 1375 | _ = try t.addIndex(.@"break", .{ |
| 1298 | 1376 | .src_node = ctx.nodeOffset(node), |
| 1299 | 1377 | .extra_index = try t.extra.append(t.gpa, Teg.Break, .{ |
| 1300 | .block = ret_block, | |
| 1378 | .block = target.block_index, | |
| 1301 | 1379 | .operand = operand, |
| 1302 | 1380 | }), |
| 1303 | 1381 | }); |
| ... | ... | @@ -1319,7 +1397,7 @@ fn transformExpression( |
| 1319 | 1397 | |
| 1320 | 1398 | var break_result = block.result_type.*; |
| 1321 | 1399 | const operand: Inst.Ref = if (brk_node.value.unwrap()) |expr| |
| 1322 | try t.transformExpression(ctx.withReturn(null), &break_result, expr) orelse | |
| 1400 | try t.transformExpression(ctx, &break_result, expr) orelse | |
| 1323 | 1401 | return null |
| 1324 | 1402 | else |
| 1325 | 1403 | .void; |
| ... | ... | @@ -1441,6 +1519,74 @@ fn transformExpression( |
| 1441 | 1519 | }); |
| 1442 | 1520 | }, |
| 1443 | 1521 | |
| 1522 | inline .fn_decl, .fn_expr => |tag| { | |
| 1523 | const data = switch (tag) { | |
| 1524 | .fn_decl => t.ast.fullFnDecl(node), | |
| 1525 | .fn_expr => t.ast.fullFnExpr(node), | |
| 1526 | else => @compileError(unreachable), | |
| 1527 | }; | |
| 1528 | ||
| 1529 | try t.pushScope(); | |
| 1530 | defer t.popScope(); | |
| 1531 | ||
| 1532 | try t.pushCaptureScope(@enumFromInt(t.insts.len - 1)); | |
| 1533 | defer t.popCaptureScope(); | |
| 1534 | ||
| 1535 | const param_list = brk: { | |
| 1536 | const scratch = t.scratch.mark(); | |
| 1537 | defer scratch.deinit(); | |
| 1538 | ||
| 1539 | for (data.params) |param| { | |
| 1540 | try t.scratch.add(t.arena, Inst.Ref, try t.transformParamDecl(ctx, param)); | |
| 1541 | } | |
| 1542 | ||
| 1543 | break :brk try t.scratchToOptionalRefList(scratch); | |
| 1544 | }; | |
| 1545 | ||
| 1546 | const return_type = if (data.return_type) |return_type_node| | |
| 1547 | try t.transformToBlock( | |
| 1548 | ctx.withReturn(null), | |
| 1549 | return_type_node, | |
| 1550 | .untracked_result, | |
| 1551 | .{ .ref = .type_type }, | |
| 1552 | .run_time, | |
| 1553 | ) | |
| 1554 | else | |
| 1555 | null; | |
| 1556 | ||
| 1557 | const body = try t.transformToBlock( | |
| 1558 | ctx, | |
| 1559 | data.body, | |
| 1560 | .function_body, | |
| 1561 | if (return_type) |ref| | |
| 1562 | .{ .ref = ref } | |
| 1563 | else | |
| 1564 | .none, | |
| 1565 | .run_time, | |
| 1566 | ); | |
| 1567 | ||
| 1568 | const inst_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(body); | |
| 1569 | ||
| 1570 | const capture_scope = t.captureScope(); | |
| 1571 | const captures_len = capture_scope.symbol_uses.count(); | |
| 1572 | const captures_data = try t.extra.alloc(t.gpa, captures_len); | |
| 1573 | @memcpy( | |
| 1574 | t.extra.list.items[captures_data..][0..captures_len], | |
| 1575 | @as([]u32, @ptrCast(capture_scope.symbol_uses.entries.items(.key))), | |
| 1576 | ); | |
| 1577 | ||
| 1578 | return try t.addRef(.func, .{ | |
| 1579 | .src_node = ctx.nodeOffset(node), | |
| 1580 | .extra_index = try t.extra.append(t.gpa, Teg.Func, .{ | |
| 1581 | .params = param_list, | |
| 1582 | .return_type = if (return_type) |ref| ref.toOptional() else .none, | |
| 1583 | .body = body, | |
| 1584 | .instruction_count = inst_count, | |
| 1585 | .captures = .{ .start = captures_data, .len = @intCast(captures_len) }, | |
| 1586 | }), | |
| 1587 | }); | |
| 1588 | }, | |
| 1589 | ||
| 1444 | 1590 | else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}), |
| 1445 | 1591 | } |
| 1446 | 1592 | } |
| ... | ... | @@ -1467,9 +1613,9 @@ fn transformExpressionList(t: *Transform, decl: BlockContext, result: ResultType |
| 1467 | 1613 | /// This returns the assignment target's Ref, as well as a result type to visit |
| 1468 | 1614 | /// the right-hand side with. Note that only a few expressions are valid here: |
| 1469 | 1615 | /// |
| 1470 | /// - A plain identifier, which is resolved directly to a `memory` instruction | |
| 1471 | /// - A `field` node: TODO | |
| 1472 | /// - An `index` node: TODO | |
| 1616 | /// - A plain identifier with a `memory` instruction -> `.store_memory` | |
| 1617 | /// - A plain identifier to a .variable_declaration -> `.store_decl` | |
| 1618 | /// - A `field` or `index` node -> `.store_field` | |
| 1473 | 1619 | /// |
| 1474 | 1620 | /// Note that these three are the root nodes, it is legal to have: |
| 1475 | 1621 | /// |
| ... | ... | @@ -1479,9 +1625,9 @@ fn transformExpressionList(t: *Transform, decl: BlockContext, result: ResultType |
| 1479 | 1625 | /// or a value with a reference type; and optionally a sub property. |
| 1480 | 1626 | fn transformAssignmentTarget( |
| 1481 | 1627 | t: *Transform, |
| 1482 | decl: BlockContext, | |
| 1628 | ctx: BlockContext, | |
| 1483 | 1629 | node: NodeIndex, |
| 1484 | ) Allocator.Error!?struct { Inst.Index, ResultType } { | |
| 1630 | ) Allocator.Error!?struct { AssignmentTarget, ResultType } { | |
| 1485 | 1631 | const node_tag = t.ast.nodes.items(.tag)[node]; |
| 1486 | 1632 | |
| 1487 | 1633 | const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)}); |
| ... | ... | @@ -1502,32 +1648,44 @@ fn transformAssignmentTarget( |
| 1502 | 1648 | return null; |
| 1503 | 1649 | } |
| 1504 | 1650 | |
| 1505 | if (BuiltInIds.has(id)) { | |
| 1651 | if (built_in_ids.has(id)) { | |
| 1506 | 1652 | try t.report.err(.assign_builtin, .{ .node = node }); |
| 1507 | 1653 | return null; |
| 1508 | 1654 | } |
| 1509 | 1655 | |
| 1510 | const lookup: *Scope.Symbol = t.lookupId(id) orelse { | |
| 1656 | const lookup: *Symbol = t.lookupId(id) orelse { | |
| 1511 | 1657 | try t.report.err(.unknown_identifier, .{ .node = node }); |
| 1512 | 1658 | return null; |
| 1513 | 1659 | }; |
| 1514 | 1660 | |
| 1515 | return switch (lookup.data) { | |
| 1516 | .mutable => |inst| { | |
| 1517 | lookup.mutated = true; | |
| 1518 | return .{ inst, lookup.type.resolve() }; | |
| 1661 | return switch (lookup.kind) { | |
| 1662 | .mutable => { | |
| 1663 | lookup.flags.mutated = true; | |
| 1664 | return .{ | |
| 1665 | .{ .memory = lookup.value.mutable }, | |
| 1666 | lookup.resultType(), | |
| 1667 | }; | |
| 1668 | }, | |
| 1669 | .variable_declaration => { | |
| 1670 | return .{ | |
| 1671 | .{ .variable_declaration = .{ | |
| 1672 | .src_node = ctx.nodeOffset(node), | |
| 1673 | .string = try t.addNullTerminatedString(id), | |
| 1674 | } }, | |
| 1675 | lookup.resultType(), | |
| 1676 | }; | |
| 1519 | 1677 | }, |
| 1520 | 1678 | else => { |
| 1521 | lookup.used = true; | |
| 1679 | lookup.flags.used = true; | |
| 1522 | 1680 | |
| 1523 | 1681 | try t.report.err(.invalid_assignment_symbol, .{ |
| 1524 | 1682 | .node = node, |
| 1525 | .decl = lookup.node, | |
| 1526 | .kind = switch (lookup.data) { | |
| 1527 | .mutable => unreachable, | |
| 1683 | .decl = lookup.src_node, | |
| 1684 | .kind = switch (lookup.kind) { | |
| 1685 | .mutable, .variable_declaration => unreachable, | |
| 1528 | 1686 | .declaration => .declaration, |
| 1529 | 1687 | .param => .param, |
| 1530 | .immutable => switch (t.ast.nodeTag(lookup.node)) { | |
| 1688 | .immutable => switch (t.ast.nodeTag(lookup.src_node)) { | |
| 1531 | 1689 | .param_decl => .param, |
| 1532 | 1690 | else => .constant, |
| 1533 | 1691 | }, |
| ... | ... | @@ -1539,11 +1697,50 @@ fn transformAssignmentTarget( |
| 1539 | 1697 | }; |
| 1540 | 1698 | }, |
| 1541 | 1699 | |
| 1542 | // TODO: allow field expressions, but these need to emit a different store node | |
| 1700 | .field => { | |
| 1701 | const field = t.ast.nodeData(node).field; | |
| 1702 | ||
| 1703 | var no_result: ResultType = .none; | |
| 1704 | const base = try t.transformExpression(ctx, &no_result, field.base) orelse return null; | |
| 1705 | ||
| 1706 | const field_str = try t.decodeIdentifier(t.ast.tokenSlice(field.field_token)); | |
| 1707 | const field_sliced = try t.addSlicedString(field_str); | |
| 1708 | ||
| 1709 | const field_ref = try t.addRef(.string, .{ | |
| 1710 | .src_node = ctx.nodeOffset(node), | |
| 1711 | .extra_index = try t.extra.append(t.gpa, SlicedString, field_sliced), | |
| 1712 | }); | |
| 1713 | ||
| 1714 | return .{ | |
| 1715 | .{ .field = .{ | |
| 1716 | .src_node = ctx.nodeOffset(node), | |
| 1717 | .base = base, | |
| 1718 | .field = field_ref, | |
| 1719 | } }, | |
| 1720 | .none, // TODO: | |
| 1721 | }; | |
| 1722 | }, | |
| 1723 | ||
| 1724 | .index => { | |
| 1725 | const bin = t.ast.nodeData(node).bin; | |
| 1726 | ||
| 1727 | var no_result: ResultType = .none; | |
| 1728 | const base = try t.transformExpression(ctx, &no_result, bin.lhs) orelse return null; | |
| 1729 | const field = try t.transformExpression(ctx, &no_result, bin.rhs) orelse return null; | |
| 1730 | ||
| 1731 | return .{ | |
| 1732 | .{ .field = .{ | |
| 1733 | .src_node = ctx.nodeOffset(node), | |
| 1734 | .base = base, | |
| 1735 | .field = field, | |
| 1736 | } }, | |
| 1737 | .none, // TODO: | |
| 1738 | }; | |
| 1739 | }, | |
| 1543 | 1740 | |
| 1544 | 1741 | else => { |
| 1545 | 1742 | var result_type: ResultType = .none; |
| 1546 | _ = try t.transformExpression(decl, &result_type, node); | |
| 1743 | _ = try t.transformExpression(ctx, &result_type, node); | |
| 1547 | 1744 | |
| 1548 | 1745 | try t.report.err(.invalid_assignment_target, .{ |
| 1549 | 1746 | .node = node, |
| ... | ... | @@ -1554,6 +1751,63 @@ fn transformAssignmentTarget( |
| 1554 | 1751 | } |
| 1555 | 1752 | } |
| 1556 | 1753 | |
| 1754 | const AssignmentTarget = union(enum) { | |
| 1755 | memory: Inst.Index, | |
| 1756 | variable_declaration: struct { | |
| 1757 | src_node: i32, | |
| 1758 | string: NullTerminatedString, | |
| 1759 | }, | |
| 1760 | field: struct { | |
| 1761 | src_node: i32, | |
| 1762 | base: Inst.Ref, | |
| 1763 | field: Inst.Ref, | |
| 1764 | }, | |
| 1765 | ||
| 1766 | fn toRef(target: AssignmentTarget, t: *Transform) !Inst.Ref { | |
| 1767 | return switch (target) { | |
| 1768 | .memory => |memory| memory.toRef(), | |
| 1769 | .variable_declaration => |var_decl| try t.addRef(.load_decl, .{ | |
| 1770 | .src_node = var_decl.src_node, | |
| 1771 | .string = var_decl.string, | |
| 1772 | }), | |
| 1773 | .field => |field| try t.addRef(.field, .{ | |
| 1774 | .src_node = field.src_node, | |
| 1775 | .extra_index = try t.extra.append(t.gpa, Teg.Bin, .{ | |
| 1776 | .lhs = field.base, | |
| 1777 | .rhs = field.field, | |
| 1778 | }), | |
| 1779 | }), | |
| 1780 | }; | |
| 1781 | } | |
| 1782 | ||
| 1783 | fn emit(target: AssignmentTarget, t: *Transform, rhs: Inst.Ref, ctx: BlockContext, node: NodeIndex) !Inst.Ref { | |
| 1784 | return switch (target) { | |
| 1785 | .memory => |memory| try t.addRef(.store, .{ | |
| 1786 | .src_node = ctx.nodeOffset(node), | |
| 1787 | .extra_index = try t.extra.append(t.gpa, Teg.Store, .{ | |
| 1788 | .memory = memory, | |
| 1789 | .operand = rhs, | |
| 1790 | }), | |
| 1791 | }), | |
| 1792 | .variable_declaration => |var_decl| try t.addRef(.store_decl, .{ | |
| 1793 | .src_node = ctx.nodeOffset(node), | |
| 1794 | .extra_index = try t.extra.append(t.gpa, Teg.StoreVarDecl, .{ | |
| 1795 | .var_decl = var_decl.string, | |
| 1796 | .operand = rhs, | |
| 1797 | }), | |
| 1798 | }), | |
| 1799 | .field => |field| try t.addRef(.store_field, .{ | |
| 1800 | .src_node = ctx.nodeOffset(node), | |
| 1801 | .extra_index = try t.extra.append(t.gpa, Teg.StoreField, .{ | |
| 1802 | .base = field.base, | |
| 1803 | .field = field.field, | |
| 1804 | .operand = rhs, | |
| 1805 | }), | |
| 1806 | }), | |
| 1807 | }; | |
| 1808 | } | |
| 1809 | }; | |
| 1810 | ||
| 1557 | 1811 | fn transformBlockStatement( |
| 1558 | 1812 | t: *Transform, |
| 1559 | 1813 | ctx: BlockContext, |
| ... | ... | @@ -1592,7 +1846,7 @@ fn transformBlockStatement( |
| 1592 | 1846 | return .void; |
| 1593 | 1847 | } |
| 1594 | 1848 | |
| 1595 | if (BuiltInIds.has(inner_id)) { | |
| 1849 | if (built_in_ids.has(inner_id)) { | |
| 1596 | 1850 | try t.report.warn(.discard_builtin, .{ .node = var_decl.init }); |
| 1597 | 1851 | return .void; |
| 1598 | 1852 | } |
| ... | ... | @@ -1602,7 +1856,7 @@ fn transformBlockStatement( |
| 1602 | 1856 | return .void; |
| 1603 | 1857 | }; |
| 1604 | 1858 | |
| 1605 | lookup.discarded = node; | |
| 1859 | lookup.discarded = .init(node); | |
| 1606 | 1860 | |
| 1607 | 1861 | return .void; |
| 1608 | 1862 | }, |
| ... | ... | @@ -1628,7 +1882,7 @@ fn transformBlockStatement( |
| 1628 | 1882 | assert(isSliceInBuffer(u8, t.ast.utf8_source, id)); |
| 1629 | 1883 | } |
| 1630 | 1884 | |
| 1631 | if (BuiltInIds.has(id)) { | |
| 1885 | if (built_in_ids.has(id)) { | |
| 1632 | 1886 | try t.report.err(.shadow_builtin_variable, .{ .node = node }); |
| 1633 | 1887 | return .void; |
| 1634 | 1888 | } |
| ... | ... | @@ -1674,20 +1928,22 @@ fn transformBlockStatement( |
| 1674 | 1928 | } |
| 1675 | 1929 | |
| 1676 | 1930 | gop.value_ptr.* = .{ |
| 1677 | .node = node, | |
| 1678 | .data = if (var_decl.is_const) | |
| 1931 | .src_node = node, | |
| 1932 | .kind = if (var_decl.is_const) .immutable else .mutable, | |
| 1933 | .value = if (var_decl.is_const) | |
| 1679 | 1934 | .{ .immutable = data_coerced_ref } |
| 1680 | 1935 | else |
| 1681 | .{ | |
| 1682 | .mutable = try t.addIndex(.memory, .{ | |
| 1683 | .src_node = ctx.nodeOffset(node), | |
| 1684 | .operand = data_coerced_ref, | |
| 1685 | }), | |
| 1686 | }, | |
| 1687 | .type = if (type_ref) |ty| | |
| 1688 | .{ .present = ty } | |
| 1689 | else | |
| 1690 | .{ .missing = data_ref }, | |
| 1936 | .{ .mutable = try t.addIndex(.memory, .{ | |
| 1937 | .src_node = ctx.nodeOffset(node), | |
| 1938 | .operand = data_coerced_ref, | |
| 1939 | }) }, | |
| 1940 | .discarded = .none, | |
| 1941 | .flags = .{ | |
| 1942 | .result_type_kind = if (type_ref != null) .present else .missing, | |
| 1943 | .used = false, | |
| 1944 | .mutated = false, | |
| 1945 | }, | |
| 1946 | .result_type_ref = if (type_ref) |ty| ty else data_ref, | |
| 1691 | 1947 | }; |
| 1692 | 1948 | |
| 1693 | 1949 | return .void; |
| ... | ... | @@ -1767,7 +2023,7 @@ fn transformBlockStatement( |
| 1767 | 2023 | fn addUntracked(t: *Transform, comptime tag: Teg.Tag, data: tag.Data()) !Inst.Index { |
| 1768 | 2024 | try t.insts.append(t.gpa, .{ |
| 1769 | 2025 | .tag = tag, |
| 1770 | .data = EncodedData.encode(tag, data), | |
| 2026 | .data = .encode(tag, data), | |
| 1771 | 2027 | }); |
| 1772 | 2028 | return @enumFromInt(t.insts.len - 1); |
| 1773 | 2029 | } |
| ... | ... | @@ -1796,6 +2052,38 @@ fn scope(t: *Transform) *Scope { |
| 1796 | 2052 | return &t.scopes.items[t.scopes.items.len - 1]; |
| 1797 | 2053 | } |
| 1798 | 2054 | |
| 2055 | fn pushCaptureScope(t: *Transform, start_index: Inst.Index) !void { | |
| 2056 | try t.capture_scopes.append(t.arena, .{ .start_index = start_index }); | |
| 2057 | } | |
| 2058 | ||
| 2059 | fn popCaptureScope(t: *Transform) void { | |
| 2060 | t.captureScope().deinit(t); | |
| 2061 | t.capture_scopes.items[t.capture_scopes.items.len - 1] = undefined; | |
| 2062 | t.capture_scopes.items.len -= 1; | |
| 2063 | } | |
| 2064 | ||
| 2065 | fn captureScope(t: *Transform) *CaptureScope { | |
| 2066 | return &t.capture_scopes.items[t.capture_scopes.items.len - 1]; | |
| 2067 | } | |
| 2068 | ||
| 2069 | fn markCapturedRef(t: *Transform, ref: Inst.Ref) !void { | |
| 2070 | switch (ref.decode()) { | |
| 2071 | .value => {}, | |
| 2072 | .index => |idx| { | |
| 2073 | var i = t.capture_scopes.items.len; | |
| 2074 | while (i > 0) { | |
| 2075 | i -= 1; | |
| 2076 | const s = &t.capture_scopes.items[i]; | |
| 2077 | ||
| 2078 | if (@intFromEnum(idx) > @intFromEnum(s.start_index)) | |
| 2079 | break; | |
| 2080 | ||
| 2081 | try s.symbol_uses.put(t.arena, idx, {}); | |
| 2082 | } | |
| 2083 | }, | |
| 2084 | } | |
| 2085 | } | |
| 2086 | ||
| 1799 | 2087 | fn getInstData(t: *Transform, index: Inst.Index) *EncodedData { |
| 1800 | 2088 | return &t.insts.items(.data)[@intFromEnum(index)]; |
| 1801 | 2089 | } |
| ... | ... | @@ -1820,7 +2108,17 @@ fn scratchToIndexList(t: *Transform, scratch: core.U32Stack.Mark) !IndexList { |
| 1820 | 2108 | }; |
| 1821 | 2109 | } |
| 1822 | 2110 | |
| 1823 | fn lookupId(t: *Transform, id: []const u8) ?*Scope.Symbol { | |
| 2111 | fn scratchToOptionalRefList(t: *Transform, scratch_top: core.U32Stack.Mark) !OptionalRefList { | |
| 2112 | const items = scratch_top.slice(u32); | |
| 2113 | const extra_top = t.extra.list.items.len; | |
| 2114 | try t.extra.list.appendSlice(t.gpa, items); | |
| 2115 | return .{ | |
| 2116 | .start = @intCast(extra_top), | |
| 2117 | .len = @intCast(items.len), | |
| 2118 | }; | |
| 2119 | } | |
| 2120 | ||
| 2121 | fn lookupId(t: *Transform, id: []const u8) ?*Symbol { | |
| 1824 | 2122 | var i = t.scopes.items.len; |
| 1825 | 2123 | while (i > 0) { |
| 1826 | 2124 | i -= 1; |
| ... | ... | @@ -1835,11 +2133,12 @@ fn addNullTerminatedString(t: *Transform, str: []const u8) !NullTerminatedString |
| 1835 | 2133 | if (str.len == 0) { |
| 1836 | 2134 | return .empty; |
| 1837 | 2135 | } |
| 2136 | ||
| 1838 | 2137 | // loop through strings trying to find an existing match, and dedupe memory |
| 1839 | 2138 | var pos: usize = 0; |
| 1840 | 2139 | while (mem.indexOfScalarPos(u8, str, pos, 0)) |i| : (pos = i + 1) { |
| 1841 | 2140 | const start_index = i - str.len; |
| 1842 | if (mem.eql(u8, t.string_bytes.items[start_index..][0..str.len], str)) { | |
| 2141 | if (mem.eql(u8, t.string_bytes.items[start_index..][0..str.len :0], str)) { | |
| 1843 | 2142 | const string: NullTerminatedString = @enumFromInt(pos); |
| 1844 | 2143 | assert(mem.eql(u8, string.slice(t.string_bytes.items), str)); |
| 1845 | 2144 | return string; |
| ... | ... | @@ -1862,7 +2161,7 @@ fn addSlicedString(t: *Transform, str: []const u8) !SlicedString { |
| 1862 | 2161 | // loop through strings trying to find an existing match, and dedupe memory |
| 1863 | 2162 | var pos: usize = 0; |
| 1864 | 2163 | while (mem.indexOfScalarPos(u8, str, pos, 0)) |i| : (pos = i + 1) { |
| 1865 | const start_index = i - str.len; | |
| 2164 | const start_index = i - str.len - 1; | |
| 1866 | 2165 | if (mem.eql(u8, t.string_bytes.items[start_index..][0..str.len], str)) { |
| 1867 | 2166 | const string: SlicedString = .{ |
| 1868 | 2167 | .start = @intCast(start_index), |
| ... | ... | @@ -1950,12 +2249,13 @@ const NullTerminatedString = Teg.NullTerminatedString; |
| 1950 | 2249 | const SlicedString = Teg.SlicedString; |
| 1951 | 2250 | const RefList = Teg.RefList; |
| 1952 | 2251 | const IndexList = Teg.IndexList; |
| 2252 | const OptionalRefList = Teg.OptionalRefList; | |
| 1953 | 2253 | const EncodedData = Teg.EncodedData; |
| 1954 | 2254 | const ExtraIndex = Teg.ExtraIndex; |
| 1955 | 2255 | |
| 1956 | const extra = core.array; | |
| 1957 | const ExtraStore = extra.ExtraStore(.teg, .@"var"); | |
| 1958 | const Float = extra.Float; | |
| 2256 | const array = core.array; | |
| 2257 | const ExtraStore = array.ExtraStore(.teg, .@"var"); | |
| 2258 | const Float = array.Float; | |
| 1959 | 2259 | |
| 1960 | 2260 | const Tokenizer = core.Tokenizer; |
| 1961 | 2261 | const Token = Tokenizer.Token; |
src/core/array.zig+38-19| ... | ... | @@ -21,12 +21,12 @@ fn setAsU32(items: []u32, comptime T: type, index: usize, payload: T) void { |
| 21 | 21 | |
| 22 | 22 | else => brk: { |
| 23 | 23 | const info = @typeInfo(field.type); |
| 24 | if (info == .Enum) { | |
| 24 | if (info == .@"enum") { | |
| 25 | 25 | items[i] = @intFromEnum(value); |
| 26 | 26 | break :brk; |
| 27 | 27 | } |
| 28 | 28 | |
| 29 | const struct_data = info.Struct; | |
| 29 | const struct_data = info.@"struct"; | |
| 30 | 30 | if (struct_data.layout == .@"packed") { |
| 31 | 31 | items[i] = @bitCast(value); |
| 32 | 32 | break :brk; |
| ... | ... | @@ -48,11 +48,11 @@ fn getAsU32(items: []const u32, comptime T: type, index: usize) T { |
| 48 | 48 | |
| 49 | 49 | else => brk: { |
| 50 | 50 | const info = @typeInfo(T); |
| 51 | if (info == .Enum) { | |
| 51 | if (info == .@"enum") { | |
| 52 | 52 | break :brk @enumFromInt(items[index]); |
| 53 | 53 | } |
| 54 | 54 | |
| 55 | const struct_data = info.Struct; | |
| 55 | const struct_data = info.@"struct"; | |
| 56 | 56 | if (struct_data.layout == .@"packed") { |
| 57 | 57 | break :brk @bitCast(items[index]); |
| 58 | 58 | } |
| ... | ... | @@ -99,9 +99,9 @@ pub const StoreKind = enum { |
| 99 | 99 | comptime kind: StoreKind, |
| 100 | 100 | comptime Owner: type, |
| 101 | 101 | ) type { |
| 102 | const ptr = @typeInfo(Owner).Pointer; | |
| 102 | const ptr = @typeInfo(Owner).pointer; | |
| 103 | 103 | assert(ptr.size == .One); |
| 104 | const mutability = brk: for (@typeInfo(ptr.child).Struct.fields) |field| { | |
| 104 | const mutability = brk: for (@typeInfo(ptr.child).@"struct".fields) |field| { | |
| 105 | 105 | if (std.mem.eql(u8, field.name, "extra")) { |
| 106 | 106 | break :brk field.type.mutability; |
| 107 | 107 | } |
| ... | ... | @@ -148,15 +148,31 @@ pub fn EncodedData(comptime Tag: type, backing_type: type) type { |
| 148 | 148 | |
| 149 | 149 | pub fn decode(data: @This(), comptime tag: Tag, proof: Tag) tag.Data() { |
| 150 | 150 | assert(proof == tag); // incorrect memory access |
| 151 | const Data = tag.Data(); | |
| 152 | const info = @typeInfo(Data); | |
| 153 | if (info == .Enum) { | |
| 151 | return data.decodeUnsafe(tag.Data(), proof); | |
| 152 | } | |
| 153 | ||
| 154 | pub fn decodeUnsafe(data: @This(), comptime T: type, proof: Tag) T { | |
| 155 | if (core.runtime_safety) { | |
| 156 | const values = comptime brk: { | |
| 157 | const values = std.enums.values(Tag); | |
| 158 | var allowed = std.enums.EnumArray(Tag, bool).initFill(false); | |
| 159 | for (values) |val| { | |
| 160 | if (Tag == InternPool.Tag and val == .removed) continue; | |
| 161 | ||
| 162 | allowed.set(val, val.Data() == T); | |
| 163 | } | |
| 164 | break :brk allowed; | |
| 165 | }; | |
| 166 | assert(values.get(proof)); // incorrect memory access | |
| 167 | } | |
| 168 | const info = @typeInfo(T); | |
| 169 | if (info == .@"enum") { | |
| 154 | 170 | return @enumFromInt(@intFromEnum(data)); |
| 155 | 171 | } |
| 156 | if (info == .Struct) { | |
| 172 | if (info == .@"struct") { | |
| 157 | 173 | assert(backing_type == u64); |
| 158 | 174 | var fields: [2]u32 = @bitCast(@intFromEnum(data)); |
| 159 | return getAsU32(&fields, Data, 0); | |
| 175 | return getAsU32(&fields, T, 0); | |
| 160 | 176 | } |
| 161 | 177 | return @bitCast(@intFromEnum(data)); |
| 162 | 178 | } |
| ... | ... | @@ -164,10 +180,10 @@ pub fn EncodedData(comptime Tag: type, backing_type: type) type { |
| 164 | 180 | pub fn encode(comptime tag: Tag, data: tag.Data()) @This() { |
| 165 | 181 | const Data = tag.Data(); |
| 166 | 182 | const info = @typeInfo(Data); |
| 167 | if (info == .Enum) { | |
| 183 | if (info == .@"enum") { | |
| 168 | 184 | return @enumFromInt(@intFromEnum(data)); |
| 169 | 185 | } |
| 170 | if (info == .Struct) { | |
| 186 | if (info == .@"struct") { | |
| 171 | 187 | comptime assert(backing_type == u64); |
| 172 | 188 | comptime assert(@sizeOf(Data) == 8); |
| 173 | 189 | var fields: [2]u32 = undefined; |
| ... | ... | @@ -190,12 +206,12 @@ pub fn extraSizeOf(comptime T: type) usize { |
| 190 | 206 | |
| 191 | 207 | else => { |
| 192 | 208 | const info = @typeInfo(T); |
| 193 | if (info == .Enum) { | |
| 194 | comptime assert(@sizeOf(info.Enum.tag_type) <= 32); | |
| 209 | if (info == .@"enum") { | |
| 210 | comptime assert(@sizeOf(info.@"enum".tag_type) <= 32); | |
| 195 | 211 | return 1; |
| 196 | 212 | } |
| 197 | 213 | |
| 198 | const struct_data = info.Struct; | |
| 214 | const struct_data = info.@"struct"; | |
| 199 | 215 | if (struct_data.layout == .@"packed") { |
| 200 | 216 | comptime assert(struct_data.backing_integer == u32); |
| 201 | 217 | return 1; |
| ... | ... | @@ -216,13 +232,14 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil |
| 216 | 232 | return struct { |
| 217 | 233 | list: List = .{}, |
| 218 | 234 | |
| 235 | pub const empty: @This() = .{}; | |
| 236 | ||
| 219 | 237 | pub const kind = set_kind; |
| 220 | 238 | pub const mutability = set_mutability; |
| 221 | 239 | |
| 222 | 240 | const Store = @This(); |
| 223 | 241 | |
| 224 | const index_holder = ExtraWrap(kind); | |
| 225 | pub const Index = index_holder.Index; | |
| 242 | pub const Index = ExtraWrap(kind).Index; | |
| 226 | 243 | |
| 227 | 244 | pub const List = switch (mutability) { |
| 228 | 245 | .@"const" => struct { |
| ... | ... | @@ -301,7 +318,7 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil |
| 301 | 318 | pub fn writeSlice(w: *TrailWriter, slice: anytype) void { |
| 302 | 319 | const Child = std.meta.Child(@TypeOf(slice)); |
| 303 | 320 | const child = @typeInfo(Child); |
| 304 | comptime assert(Child == u32 or (child == .Enum and child.Enum.tag_type == u32)); | |
| 321 | comptime assert(Child == u32 or (child == .@"enum" and child.@"enum".tag_type == u32)); | |
| 305 | 322 | assert(w.leftover >= slice.len); |
| 306 | 323 | w.store.list.appendSliceAssumeCapacity(@ptrCast(slice)); |
| 307 | 324 | w.leftover -= slice.len; |
| ... | ... | @@ -433,6 +450,8 @@ pub const Float = struct { |
| 433 | 450 | } |
| 434 | 451 | }; |
| 435 | 452 | |
| 453 | const core = @import("mod.zig"); | |
| 454 | ||
| 436 | 455 | const std = @import("std"); |
| 437 | 456 | const assert = std.debug.assert; |
| 438 | 457 | const Allocator = std.mem.Allocator; |
src/core/js/Js.zig+1-2| ... | ... | @@ -24,7 +24,7 @@ pub fn handleFunctionAir( |
| 24 | 24 | backend: *Js, |
| 25 | 25 | comp: *Comp, |
| 26 | 26 | value: Value, |
| 27 | air: *const Air, | |
| 27 | air: Air, | |
| 28 | 28 | arena: Allocator, |
| 29 | 29 | ) !u32 { |
| 30 | 30 | var ng = try NodeGen.init(comp.gpa, arena, &comp.intern_pool, air); |
| ... | ... | @@ -55,7 +55,6 @@ const Js = @This(); |
| 55 | 55 | |
| 56 | 56 | const std = @import("std"); |
| 57 | 57 | const assert = std.debug.assert; |
| 58 | const log = std.log.scoped(.js); | |
| 59 | 58 | const Allocator = std.mem.Allocator; |
| 60 | 59 | |
| 61 | 60 | const core = @import("../mod.zig"); |
src/core/js/Link.zig+32-16| ... | ... | @@ -10,7 +10,7 @@ backend: *Js, |
| 10 | 10 | |
| 11 | 11 | /// Value is an index into `link_strings` |
| 12 | 12 | decls: std.AutoHashMapUnmanaged(InternPool.Value, LinkString.Index) = .{}, |
| 13 | link_strings: std.ArrayHashMapUnmanaged( | |
| 13 | decl_strings: std.ArrayHashMapUnmanaged( | |
| 14 | 14 | LinkString, |
| 15 | 15 | GlobalOwner, |
| 16 | 16 | LinkString.Context, |
| ... | ... | @@ -26,7 +26,6 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void { |
| 26 | 26 | // names. We also need this list prepared so that local identifier names do not |
| 27 | 27 | // shadow functions or externals. |
| 28 | 28 | const root_decl = link.comp.intern_pool.declPtrConst(entry_decl); |
| 29 | assert(root_decl.name == .empty); | |
| 30 | 29 | |
| 31 | 30 | const root_function = link.treeForFunction(root_decl.value()); |
| 32 | 31 | |
| ... | ... | @@ -39,6 +38,12 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void { |
| 39 | 38 | try dependency_queue.list.appendSlice(link.comp.gpa, root_function.top_level_value_deps); |
| 40 | 39 | |
| 41 | 40 | while (dependency_queue.next()) |value| { |
| 41 | const gop_decl = try link.decls.getOrPut(link.comp.gpa, value); | |
| 42 | if (gop_decl.found_existing) { | |
| 43 | dependency_queue.swapRemovePrevious(); | |
| 44 | continue; | |
| 45 | } | |
| 46 | ||
| 42 | 47 | const tree = link.treeForFunction(value); |
| 43 | 48 | |
| 44 | 49 | for (tree.extern_symbols) |extern_symbol| { |
| ... | ... | @@ -55,24 +60,24 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void { |
| 55 | 60 | const name = decl.name.toSlice(&link.comp.intern_pool); |
| 56 | 61 | assert(name.len > 0); |
| 57 | 62 | |
| 58 | const gop = try link.link_strings.getOrPutContextAdapted( | |
| 63 | const gop_string = try link.decl_strings.getOrPutContextAdapted( | |
| 59 | 64 | link.comp.gpa, |
| 60 | 65 | name, |
| 61 | 66 | LinkString.SliceContext{ .link = link }, |
| 62 | 67 | .{ .link = link }, |
| 63 | 68 | ); |
| 64 | if (gop.found_existing) { | |
| 65 | @panic("TODO: handle this name collision case"); | |
| 69 | if (gop_string.found_existing) { | |
| 70 | std.debug.panic("TODO: handle this name collision case: {s}", .{name}); | |
| 66 | 71 | } |
| 67 | 72 | 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)); | |
| 73 | gop_string.key_ptr.* = name_str; | |
| 74 | gop_string.value_ptr.* = .decl; | |
| 75 | gop_decl.value_ptr.* = @enumFromInt(gop_string.index); | |
| 71 | 76 | } |
| 72 | 77 | |
| 73 | 78 | try writer.writeAll(std.fmt.comptimePrint( |
| 74 | 79 | "// Language Compiler v{}. Alpha quality software.\n", |
| 75 | .{core.version}, | |
| 80 | .{@import("config").version}, | |
| 76 | 81 | )); |
| 77 | 82 | |
| 78 | 83 | if (root_function.nodes.len > 1) { |
| ... | ... | @@ -91,7 +96,8 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void { |
| 91 | 96 | |
| 92 | 97 | fn treeForFunction(link: *const Link, value: InternPool.Value) *Tree { |
| 93 | 98 | assert(value.valueTag(&link.comp.intern_pool) == .func); // must be a function |
| 94 | return link.backend.codegen_map.getPtr(value) orelse unreachable; | |
| 99 | return link.backend.codegen_map.getPtr(value) orelse | |
| 100 | unreachable; // did not codegen function | |
| 95 | 101 | } |
| 96 | 102 | |
| 97 | 103 | const DependencyQueue = struct { |
| ... | ... | @@ -106,6 +112,10 @@ const DependencyQueue = struct { |
| 106 | 112 | defer queue.i += 1; |
| 107 | 113 | return queue.list.items[queue.i]; |
| 108 | 114 | } |
| 115 | ||
| 116 | pub fn swapRemovePrevious(queue: *DependencyQueue) void { | |
| 117 | _ = queue.list.swapRemove(queue.i - 1); | |
| 118 | } | |
| 109 | 119 | }; |
| 110 | 120 | |
| 111 | 121 | pub fn deinit(link: *Link) void { |
| ... | ... | @@ -114,7 +124,7 @@ pub fn deinit(link: *Link) void { |
| 114 | 124 | |
| 115 | 125 | defer link.* = undefined; |
| 116 | 126 | link.decls.deinit(link.comp.gpa); |
| 117 | link.link_strings.deinit(link.comp.gpa); | |
| 127 | link.decl_strings.deinit(link.comp.gpa); | |
| 118 | 128 | link.string_bytes.deinit(link.comp.gpa); |
| 119 | 129 | } |
| 120 | 130 | |
| ... | ... | @@ -123,7 +133,7 @@ fn reserveExternSymbol(link: *Link, extern_symbol: String.WithLen) !void { |
| 123 | 133 | defer t.end(); |
| 124 | 134 | |
| 125 | 135 | const name = extern_symbol.toSlice(&link.comp.intern_pool); |
| 126 | const gop = try link.link_strings.getOrPutContextAdapted( | |
| 136 | const gop = try link.decl_strings.getOrPutContextAdapted( | |
| 127 | 137 | link.comp.gpa, |
| 128 | 138 | name, |
| 129 | 139 | LinkString.SliceContext{ .link = link }, |
| ... | ... | @@ -152,7 +162,7 @@ fn valueRefIdentifier(link: *const Link, value: InternPool.Value) []const u8 { |
| 152 | 162 | var t = tracer.trace(@src()); |
| 153 | 163 | defer t.end(); |
| 154 | 164 | |
| 155 | return (link.decls.get(value) orelse if (std.debug.runtime_safety) { | |
| 165 | return (link.decls.get(value) orelse if (core.runtime_safety) { | |
| 156 | 166 | std.debug.panic("Value \"{}\" is not present in top level value map", .{ |
| 157 | 167 | value.dump(&link.comp.intern_pool), |
| 158 | 168 | }); |
| ... | ... | @@ -305,6 +315,13 @@ fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytyp |
| 305 | 315 | try writer.writeAll("]"); |
| 306 | 316 | }, |
| 307 | 317 | |
| 318 | .external_value => |external_value| { | |
| 319 | for (external_value.key, 0..) |part, i| { | |
| 320 | if (i != 0) try writer.writeAll("."); | |
| 321 | try writer.writeAll(part.toSlice(&link.comp.intern_pool)); | |
| 322 | } | |
| 323 | }, | |
| 324 | ||
| 308 | 325 | else => unreachable, // this value is not a plain_value |
| 309 | 326 | } |
| 310 | 327 | } |
| ... | ... | @@ -470,7 +487,7 @@ fn printExprLevel( |
| 470 | 487 | |
| 471 | 488 | for (args.slice(tree), 0..) |arg, i| { |
| 472 | 489 | if (i != 0) try writer.writeAll(", "); |
| 473 | try link.printExpr(tree, arg, writer); | |
| 490 | try link.printExprLevel(tree, arg, .assignment, writer); | |
| 474 | 491 | } |
| 475 | 492 | |
| 476 | 493 | try writer.writeAll(")"); |
| ... | ... | @@ -806,7 +823,7 @@ const LinkString = struct { |
| 806 | 823 | _, |
| 807 | 824 | |
| 808 | 825 | pub fn toString(index: Index, link: *const Link) LinkString { |
| 809 | return link.link_strings.keys()[@intFromEnum(index)]; | |
| 826 | return link.decl_strings.keys()[@intFromEnum(index)]; | |
| 810 | 827 | } |
| 811 | 828 | }; |
| 812 | 829 | |
| ... | ... | @@ -843,7 +860,6 @@ const Link = @This(); |
| 843 | 860 | const std = @import("std"); |
| 844 | 861 | const assert = std.debug.assert; |
| 845 | 862 | const Wyhash = std.hash.Wyhash; |
| 846 | const log = std.log.scoped(.js_link); | |
| 847 | 863 | const Writer = std.io.AnyWriter; |
| 848 | 864 | |
| 849 | 865 | const core = @import("../mod.zig"); |
src/core/js/NodeGen.zig+81-52| ... | ... | @@ -1,7 +1,7 @@ |
| 1 | 1 | //! Air -> Js.Tree |
| 2 | 2 | gpa: Allocator, |
| 3 | 3 | arena: Allocator, |
| 4 | air: *const Air, | |
| 4 | air: Air, | |
| 5 | 5 | ip: *InternPool, |
| 6 | 6 | |
| 7 | 7 | nodes: Node.List, |
| ... | ... | @@ -28,12 +28,12 @@ const MapEntry = union { |
| 28 | 28 | }, |
| 29 | 29 | }; |
| 30 | 30 | |
| 31 | pub fn init(gpa: Allocator, arena: Allocator, ip: *InternPool, air: *const Air) !NodeGen { | |
| 31 | pub fn init(gpa: Allocator, arena: Allocator, ip: *InternPool, air: Air) !NodeGen { | |
| 32 | 32 | const t = tracer.trace(@src()); |
| 33 | 33 | defer t.end(); |
| 34 | 34 | |
| 35 | 35 | const air_node_map = try arena.alloc(MapEntry, air.insts.len); |
| 36 | if (std.debug.runtime_safety) { | |
| 36 | if (core.runtime_safety) { | |
| 37 | 37 | @memset(air_node_map, .{ .none = {} }); |
| 38 | 38 | } |
| 39 | 39 | const param_count = air.paramCount(); |
| ... | ... | @@ -99,8 +99,8 @@ pub fn build(c: *NodeGen) !void { |
| 99 | 99 | const t = tracer.trace(@src()); |
| 100 | 100 | defer t.end(); |
| 101 | 101 | |
| 102 | assert(c.air.instTag(.main_block) == .block); | |
| 103 | const statements = c.air.instData(.block, .main_block).slice(c.air); | |
| 102 | assert(c.air.instTag(.main_block) == .block); // expected Air to start with block | |
| 103 | const statements = c.air.instData(.block, .main_block).slice(&c.air); | |
| 104 | 104 | |
| 105 | 105 | const scratch_top = c.scratch.items.len; |
| 106 | 106 | defer c.scratch.shrinkRetainingCapacity(scratch_top); |
| ... | ... | @@ -109,11 +109,9 @@ pub fn build(c: *NodeGen) !void { |
| 109 | 109 | |
| 110 | 110 | if (statements.len > 0) { |
| 111 | 111 | for (statements[0..c.param_count], 1..) |stmt, expected_id| { |
| 112 | assert(c.air.instTag(stmt) == .arg); // expected Air to start with param nodes | |
| 112 | 113 | 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 | }; | |
| 114 | c.air_node_map[@intFromEnum(stmt)] = .{ .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }) }; | |
| 117 | 115 | } |
| 118 | 116 | |
| 119 | 117 | for (statements[c.param_count..]) |stmt| { |
| ... | ... | @@ -124,7 +122,7 @@ pub fn build(c: *NodeGen) !void { |
| 124 | 122 | const items = c.scratch.items[scratch_top..]; |
| 125 | 123 | const top = c.extra.list.items.len; |
| 126 | 124 | try c.extra.list.appendSlice(c.gpa, items); |
| 127 | c.nodes.items(.data)[0] = EncodedData.encode(.root, .{ | |
| 125 | c.nodes.items(.data)[0] = .encode(.root, .{ | |
| 128 | 126 | .start = @intCast(top), |
| 129 | 127 | .len = @intCast(items.len), |
| 130 | 128 | }); |
| ... | ... | @@ -139,13 +137,13 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 139 | 137 | |
| 140 | 138 | .block => try c.codeGenBlock(inst), |
| 141 | 139 | |
| 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 | }, | |
| 140 | // .js_global_ref => { | |
| 141 | // const data: InternPool.String.WithLen = c.air.instData(.js_global_ref, inst); | |
| 142 | // try c.extern_symbols.put(c.gpa, data, {}); | |
| 143 | // c.air_node_map[@intFromEnum(inst)] = .{ | |
| 144 | // .node = try c.add(.global, .{ .name = data }), | |
| 145 | // }; | |
| 146 | // }, | |
| 149 | 147 | |
| 150 | 148 | .memory => { |
| 151 | 149 | const i = @intFromEnum(inst); |
| ... | ... | @@ -171,7 +169,6 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 171 | 169 | .sub, |
| 172 | 170 | .mul, |
| 173 | 171 | .div, |
| 174 | .index, | |
| 175 | 172 | => |tag| { |
| 176 | 173 | const data: Air.Bin = c.air.instData(tag, inst); |
| 177 | 174 | const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{ |
| ... | ... | @@ -181,6 +178,15 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 181 | 178 | try c.markExpression(inst, node); |
| 182 | 179 | }, |
| 183 | 180 | |
| 181 | .field_array => { | |
| 182 | const data: Air.Bin = c.air.instData(.field_array, inst); | |
| 183 | const node = try c.add(.index, .{ | |
| 184 | .lhs = try c.fetchExpression(data.lhs), | |
| 185 | .rhs = try c.fetchExpression(data.rhs), | |
| 186 | }); | |
| 187 | try c.markExpression(inst, node); | |
| 188 | }, | |
| 189 | ||
| 184 | 190 | .store => { |
| 185 | 191 | const data: Air.Store = c.air.instData(.store, inst); |
| 186 | 192 | |
| ... | ... | @@ -253,10 +259,21 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 253 | 259 | }, |
| 254 | 260 | |
| 255 | 261 | .field_opaque => { |
| 256 | const data: Air.FieldOpaque = c.air.instData(.field_opaque, inst); | |
| 257 | const node = try c.add(.field, .{ | |
| 258 | .base = try c.fetchExpression(data.base), | |
| 259 | .field = data.field, | |
| 262 | const data: Air.Bin = c.air.instData(.field_opaque, inst); | |
| 263 | if (data.rhs.value()) |field_value| { | |
| 264 | if (field_value.typeOf(c.ip) == .string) { | |
| 265 | const node = try c.add(.field, .{ | |
| 266 | .base = try c.fetchExpression(data.lhs), | |
| 267 | .field = field_value.asString(c.ip).tryToNullTerminatedString(c.ip, c.gpa) catch @panic("TODO"), | |
| 268 | }); | |
| 269 | try c.markExpression(inst, node); | |
| 270 | return; | |
| 271 | } | |
| 272 | } | |
| 273 | ||
| 274 | const node = try c.add(.index, .{ | |
| 275 | .lhs = try c.fetchExpression(data.lhs), | |
| 276 | .rhs = try c.fetchExpression(data.rhs), | |
| 260 | 277 | }); |
| 261 | 278 | try c.markExpression(inst, node); |
| 262 | 279 | }, |
| ... | ... | @@ -288,7 +305,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 288 | 305 | |
| 289 | 306 | inline .call, .js_new => |tag| { |
| 290 | 307 | const data: Air.Call = c.air.instData(tag, inst); |
| 291 | const call_data = data.call_data.get(c.air); | |
| 308 | const call_data = data.call_data.get(&c.air); | |
| 292 | 309 | |
| 293 | 310 | try c.extra.list.ensureUnusedCapacity(c.gpa, call_data.args.len); |
| 294 | 311 | const start = c.extra.list.items.len; |
| ... | ... | @@ -296,7 +313,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 296 | 313 | |
| 297 | 314 | const items = c.extra.list.items[start..][0..call_data.args.len]; |
| 298 | 315 | |
| 299 | for (call_data.args.slice(c.air), items) |arg, *dest| { | |
| 316 | for (call_data.args.slice(&c.air), items) |arg, *dest| { | |
| 300 | 317 | dest.* = @intFromEnum(try c.fetchExpression(arg)); |
| 301 | 318 | } |
| 302 | 319 | |
| ... | ... | @@ -312,7 +329,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 312 | 329 | |
| 313 | 330 | .@"if" => { |
| 314 | 331 | const data: Air.If = c.air.instData(.@"if", inst); |
| 315 | const extra = data.data.get(c.air); | |
| 332 | const extra = data.data.get(&c.air); | |
| 316 | 333 | var cond = try c.fetchExpression(data.cond); |
| 317 | 334 | |
| 318 | 335 | if (c.canLowerToExpression(extra.yes) and |
| ... | ... | @@ -380,17 +397,31 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 380 | 397 | .@"break" => { |
| 381 | 398 | const brk: Air.Break = c.air.instData(.@"break", inst); |
| 382 | 399 | |
| 383 | const operand = try c.fetchExpression(brk.value); | |
| 384 | ||
| 385 | 400 | // break #0 -> return |
| 386 | if (brk.block_ref == .main_block) { | |
| 387 | try c.addStmt(try c.add(.stmt_return, .{ | |
| 388 | .operand = operand.toOptional(), | |
| 389 | })); | |
| 401 | if (brk.block == .main_block) { | |
| 402 | if (brk.value.isVoid()) { | |
| 403 | // If this is a void break at the end of the main block, nothing | |
| 404 | // needs to be emitted as that is already what happens at the end | |
| 405 | // of a block. | |
| 406 | // | |
| 407 | // This is also required for the main function, which cannot return | |
| 408 | // anything. We don't want to emit return instructions here. | |
| 409 | const main_insts = c.air.instData(.block, .main_block).slice(&c.air); | |
| 410 | if (main_insts[main_insts.len - 1] == inst) { | |
| 411 | return; | |
| 412 | } | |
| 413 | ||
| 414 | try c.addStmt(try c.add(.stmt_return, .{ .operand = .none })); | |
| 415 | } else { | |
| 416 | try c.addStmt(try c.add(.stmt_return, .{ | |
| 417 | .operand = (try c.fetchExpression(brk.value)).toOptional(), | |
| 418 | })); | |
| 419 | } | |
| 390 | 420 | } else { |
| 391 | const block = &c.air_node_map[@intFromEnum(brk.block_ref)].block; | |
| 421 | const operand = try c.fetchExpression(brk.value); | |
| 422 | const block = &c.air_node_map[@intFromEnum(brk.block)].block; | |
| 392 | 423 | |
| 393 | const block_insts = c.air.instData(.block, brk.block_ref).slice(c.air); | |
| 424 | const block_insts = c.air.instData(.block, brk.block).slice(&c.air); | |
| 394 | 425 | if (block_insts[block_insts.len - 1] != inst) { |
| 395 | 426 | block.used = true; |
| 396 | 427 | } |
| ... | ... | @@ -418,7 +449,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 418 | 449 | |
| 419 | 450 | for ( |
| 420 | 451 | struct_type.field_keys, |
| 421 | struct_init.valuesPtr(c.air), | |
| 452 | struct_init.valuesPtr(&c.air), | |
| 422 | 453 | store_offset.., |
| 423 | 454 | store_offset + property_count.., |
| 424 | 455 | ) |key, value, key_i, value_i| { |
| ... | ... | @@ -438,7 +469,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 438 | 469 | .array_init => { |
| 439 | 470 | const struct_init: Air.ArrayInit = c.air.instData(.array_init, inst); |
| 440 | 471 | |
| 441 | const items = struct_init.list.get(c.air).slice(c.air); | |
| 472 | const items = struct_init.list.get(&c.air).slice(&c.air); | |
| 442 | 473 | const store_offset = try c.extra.alloc(c.gpa, items.len); |
| 443 | 474 | |
| 444 | 475 | for ( |
| ... | ... | @@ -500,15 +531,15 @@ fn fetchInstExpression(c: *NodeGen, index: Air.Inst.Index) !Node.Index { |
| 500 | 531 | defer t.end(); |
| 501 | 532 | |
| 502 | 533 | const data = c.air_node_map[@intFromEnum(index)]; |
| 503 | if (std.debug.runtime_safety) { | |
| 534 | if (core.runtime_safety) { | |
| 504 | 535 | const flags: Air.Flags = c.air.instFlags(index); |
| 505 | 536 | switch (flags.uses) { |
| 506 | .none => if (std.debug.runtime_safety) { | |
| 537 | .none => { | |
| 507 | 538 | // incorrectly marked instruction as unused |
| 508 | 539 | std.debug.panic("called fetchInstExpression(#{d}), but no expression was generated", .{ |
| 509 | 540 | @intFromEnum(index), |
| 510 | 541 | }); |
| 511 | } else unreachable, | |
| 542 | }, | |
| 512 | 543 | .single => {}, |
| 513 | 544 | .multi => { |
| 514 | 545 | // multi-references can only duplicate identifier nodes. |
| ... | ... | @@ -543,6 +574,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { |
| 543 | 574 | .simple_value, |
| 544 | 575 | .struct_value, |
| 545 | 576 | .array_value, |
| 577 | .external_value, | |
| 546 | 578 | => return try c.add(.plain_value, .{ .value = v }), |
| 547 | 579 | |
| 548 | 580 | // Hoist functions as top level values |
| ... | ... | @@ -609,7 +641,7 @@ fn codeGenBranch(c: *NodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Bran |
| 609 | 641 | }); |
| 610 | 642 | |
| 611 | 643 | const block: Air.IndexList = c.air.instData(.block, i); |
| 612 | for (block.slice(c.air)) |item| { | |
| 644 | for (block.slice(&c.air)) |item| { | |
| 613 | 645 | try c.codeGenInst(item); |
| 614 | 646 | } |
| 615 | 647 | |
| ... | ... | @@ -661,7 +693,7 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void { |
| 661 | 693 | }; |
| 662 | 694 | const block: Air.IndexList = c.air.instData(.block, inst); |
| 663 | 695 | |
| 664 | for (block.slice(c.air)) |item| { | |
| 696 | for (block.slice(&c.air)) |item| { | |
| 665 | 697 | try c.codeGenInst(item); |
| 666 | 698 | } |
| 667 | 699 | |
| ... | ... | @@ -689,12 +721,12 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { |
| 689 | 721 | return switch (inst.decode()) { |
| 690 | 722 | .value => true, |
| 691 | 723 | .index => |index| switch (c.air.instTag(index)) { |
| 692 | .js_global_ref, | |
| 693 | 724 | .call, |
| 694 | 725 | .js_new, |
| 695 | 726 | .arg, |
| 696 | 727 | .field_opaque, |
| 697 | 728 | .field_struct, |
| 729 | .field_array, | |
| 698 | 730 | .add, |
| 699 | 731 | .sub, |
| 700 | 732 | .mul, |
| ... | ... | @@ -707,7 +739,6 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { |
| 707 | 739 | .store, |
| 708 | 740 | .struct_init, |
| 709 | 741 | .array_init, |
| 710 | .index, | |
| 711 | 742 | .array_len, |
| 712 | 743 | => true, |
| 713 | 744 | |
| ... | ... | @@ -728,7 +759,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { |
| 728 | 759 | |
| 729 | 760 | .@"if" => { |
| 730 | 761 | const call = c.air.instData(.@"if", index); |
| 731 | const data = call.data.get(c.air); | |
| 762 | const data = call.data.get(&c.air); | |
| 732 | 763 | |
| 733 | 764 | return c.canLowerToExpression(call.cond) and |
| 734 | 765 | c.canLowerToExpression(data.yes) and |
| ... | ... | @@ -758,7 +789,7 @@ fn add(c: *NodeGen, comptime tag: Tree.Tag, data: tag.Data()) !Node.Index { |
| 758 | 789 | |
| 759 | 790 | try c.nodes.append(c.gpa, .{ |
| 760 | 791 | .tag = tag, |
| 761 | .data = EncodedData.encode(tag, data), | |
| 792 | .data = .encode(tag, data), | |
| 762 | 793 | }); |
| 763 | 794 | return @enumFromInt(c.nodes.len - 1); |
| 764 | 795 | } |
| ... | ... | @@ -776,19 +807,17 @@ const std = @import("std"); |
| 776 | 807 | const assert = std.debug.assert; |
| 777 | 808 | const Allocator = std.mem.Allocator; |
| 778 | 809 | |
| 779 | const InternPool = @import("../InternPool.zig"); | |
| 810 | const core = @import("../mod.zig"); | |
| 811 | const InternPool = core.InternPool; | |
| 780 | 812 | const Value = InternPool.Value; |
| 781 | 813 | const NullTerminatedString = InternPool.NullTerminatedString; |
| 782 | ||
| 783 | const Comp = @import("../Comp.zig"); | |
| 814 | const Comp = core.Comp; | |
| 784 | 815 | const Decl = Comp.Decl; |
| 785 | ||
| 786 | const Tree = @import("Tree.zig"); | |
| 816 | const Tree = core.Js.Tree; | |
| 787 | 817 | const EncodedData = Tree.EncodedData; |
| 788 | 818 | const Node = Tree.Node; |
| 789 | 819 | const LocalIndex = Tree.LocalIndex; |
| 820 | const Air = core.Air; | |
| 821 | const dod = core.array; | |
| 790 | 822 | |
| 791 | const Air = @import("../Air.zig"); | |
| 792 | ||
| 793 | const dod = @import("../array.zig"); | |
| 794 | 823 | const tracer = @import("tracer"); |
src/core/js/Tree.zig+3-3| ... | ... | @@ -10,8 +10,8 @@ top_level_value_deps: []Value, |
| 10 | 10 | extra: ExtraStore, |
| 11 | 11 | |
| 12 | 12 | pub const LocalIndex = u32; |
| 13 | pub const ExtraStore = extra.ExtraStore(.js_tree, .@"const"); | |
| 14 | pub const EncodedData = extra.EncodedData(Tag, u64); | |
| 13 | pub const ExtraStore = array.ExtraStore(.js_tree, .@"const"); | |
| 14 | pub const EncodedData = array.EncodedData(Tag, u64); | |
| 15 | 15 | pub const ExtraIndex = ExtraStore.Index; |
| 16 | 16 | |
| 17 | 17 | pub const Tag = enum { |
| ... | ... | @@ -299,6 +299,6 @@ const String = InternPool.String; |
| 299 | 299 | const Comp = @import("../Comp.zig"); |
| 300 | 300 | const Decl = Comp.Decl; |
| 301 | 301 | |
| 302 | const extra = @import("../array.zig"); | |
| 302 | const array = @import("../array.zig"); | |
| 303 | 303 | |
| 304 | 304 | const Unused = @import("../Ast.zig").Unused; |
src/core/mod.zig+61-34| ... | ... | @@ -2,57 +2,35 @@ |
| 2 | 2 | |
| 3 | 3 | // sort-lines: start |
| 4 | 4 | |
| 5 | /// Analyzed Intermediate Representation. | |
| 6 | 5 | pub const Air = @import("Air.zig"); |
| 7 | ||
| 8 | 6 | pub const array = @import("array.zig"); |
| 9 | ||
| 10 | /// Abstract Syntax Tree - Preserves all syntax forms. | |
| 11 | 7 | pub const Ast = @import("Ast.zig"); |
| 12 | ||
| 13 | 8 | pub const Cache = @import("Cache.zig"); |
| 14 | ||
| 15 | /// Language Compilation Unit | |
| 16 | 9 | pub const Comp = @import("Comp.zig"); |
| 17 | ||
| 18 | /// Storage for shared objects (`Value`/`Decl`/`Namespace`) in a compilation | |
| 19 | 10 | pub const InternPool = @import("InternPool.zig"); |
| 20 | ||
| 21 | /// JavaScript backend and related utilities | |
| 22 | 11 | pub const Js = @import("js/Js.zig"); |
| 23 | ||
| 24 | /// Recursive descent parser. Produces `Ast` | |
| 25 | 12 | pub const Parse = @import("Parse.zig"); |
| 26 | ||
| 27 | 13 | pub const Report = @import("Report.zig"); |
| 28 | ||
| 29 | /// Semantic Analysis visits a `Teg` declaration and produces `Air` | |
| 30 | 14 | pub const Sema = @import("Sema.zig"); |
| 31 | ||
| 32 | 15 | pub const Stack = @import("stack.zig").Stack; |
| 33 | 16 | pub const U32Stack = Stack(u32); |
| 34 | ||
| 35 | 17 | pub const StoredTrace = @import("StoredTrace.zig"); |
| 36 | 18 | pub const DebugStoredTrace = StoredTrace.DebugStoredTrace; |
| 37 | ||
| 38 | /// Untyped Intermediate Representation. Flat instruction list | |
| 19 | pub const Queue = @import("queue.zig").Queue; | |
| 39 | 20 | pub const Teg = @import("Teg.zig"); |
| 40 | ||
| 41 | /// Iterate tokens from a `[:0]const u8`. Includes code to decode/encode string escapes. | |
| 42 | 21 | pub const Tokenizer = @import("Tokenizer.zig"); |
| 43 | ||
| 44 | /// Transform pass converts `Ast` into `Teg` | |
| 45 | 22 | pub const Transform = @import("Transform.zig"); |
| 46 | ||
| 47 | 23 | pub const unicode_props = @import("unicode_props.zig"); |
| 48 | 24 | |
| 49 | 25 | // sort-lines: end |
| 50 | 26 | |
| 51 | // The rest of this file is utility functions, which are not specific to any of | |
| 52 | // the above namespaces. Remember Zig discourages namespaces like `utils` | |
| 27 | // The rest of this file is small utility functions, which are not specific to | |
| 28 | // any of the above namespaces. Remember Zig discourages namespaces like `utils`. | |
| 53 | 29 | |
| 54 | /// Language SDK version according to the latest entry in `CHANGELOG.md` | |
| 55 | pub const version = @import("build_options").version; | |
| 30 | pub const runtime_safety = switch (@import("builtin").mode) { | |
| 31 | .Debug, .ReleaseSafe => true, | |
| 32 | .ReleaseFast, .ReleaseSmall => false, | |
| 33 | }; | |
| 56 | 34 | |
| 57 | 35 | /// This is an alternative to `std.debug.assert`, where you want to print some |
| 58 | 36 | /// debug info instead of "reached unreachable code". Usage is as follows: |
| ... | ... | @@ -64,7 +42,7 @@ pub const version = @import("build_options").version; |
| 64 | 42 | /// This allows you to perform any number of side effects in the failure case |
| 65 | 43 | /// with certainty that they are not analyzed in unsafe modes. |
| 66 | 44 | pub inline fn assertFailed(cond: bool) bool { |
| 67 | if (std.debug.runtime_safety) { | |
| 45 | if (runtime_safety) { | |
| 68 | 46 | return !cond; |
| 69 | 47 | } else if (!cond) { |
| 70 | 48 | unreachable; // assertion failure |
| ... | ... | @@ -74,13 +52,13 @@ pub inline fn assertFailed(cond: bool) bool { |
| 74 | 52 | /// If `value` can be cast to `i32` |
| 75 | 53 | pub fn inInt32Range(value: anytype) bool { |
| 76 | 54 | return switch (@typeInfo(@TypeOf(value))) { |
| 77 | .Int, | |
| 78 | .ComptimeInt, | |
| 55 | .int, | |
| 56 | .comptime_int, | |
| 79 | 57 | => value >= std.math.minInt(i32) and |
| 80 | 58 | value <= std.math.maxInt(i32), |
| 81 | 59 | |
| 82 | .Float, | |
| 83 | .ComptimeFloat, | |
| 60 | .float, | |
| 61 | .comptime_float, | |
| 84 | 62 | => std.math.isFinite(value) and |
| 85 | 63 | value >= std.math.minInt(i32) and |
| 86 | 64 | value <= std.math.maxInt(i32) and |
| ... | ... | @@ -134,6 +112,16 @@ pub const OutStream = struct { |
| 134 | 112 | }; |
| 135 | 113 | } |
| 136 | 114 | |
| 115 | var stderr_writer: std.fs.File.Writer = undefined; | |
| 116 | pub fn getStderr() OutStream { | |
| 117 | const stderr = std.io.getStdErr(); | |
| 118 | stderr_writer = stderr.writer(); | |
| 119 | return .{ | |
| 120 | .writer = stderr_writer.any(), | |
| 121 | .tty = std.io.tty.detectConfig(stderr), | |
| 122 | }; | |
| 123 | } | |
| 124 | ||
| 137 | 125 | pub fn print(out: OutStream, comptime fmt: []const u8, args: anytype) Error!void { |
| 138 | 126 | out.writer.print(fmt, args) catch |e| switch (e) { |
| 139 | 127 | error.OutOfMemory => return Error.OutOfMemory, |
| ... | ... | @@ -149,9 +137,48 @@ pub const OutStream = struct { |
| 149 | 137 | } |
| 150 | 138 | }; |
| 151 | 139 | |
| 140 | pub const SerializeTarget = union(enum) { | |
| 141 | file: std.fs.File, | |
| 142 | writer: std.io.AnyWriter, | |
| 143 | ||
| 144 | pub fn writev(target: SerializeTarget, vecs: []std.posix.iovec_const) !void { | |
| 145 | return switch (target) { | |
| 146 | .file => |file| try file.writevAll(vecs), | |
| 147 | .writer => |w| for (vecs) |iov| { | |
| 148 | try w.writeAll(iov.base[0..iov.len]); | |
| 149 | }, | |
| 150 | }; | |
| 151 | } | |
| 152 | }; | |
| 153 | ||
| 154 | pub fn iovec(slice: anytype) if (@typeInfo(@TypeOf(slice)).pointer.is_const) | |
| 155 | std.posix.iovec_const | |
| 156 | else | |
| 157 | std.posix.iovec { | |
| 158 | return .{ .base = slice.ptr, .len = slice.len }; | |
| 159 | } | |
| 160 | pub fn iovecConst(slice: []const u8) std.posix.iovec_const { | |
| 161 | return .{ .base = slice.ptr, .len = slice.len }; | |
| 162 | } | |
| 163 | ||
| 152 | 164 | const std = @import("std"); |
| 153 | 165 | const assert = std.debug.assert; |
| 154 | 166 | |
| 155 | 167 | comptime { |
| 156 | 168 | std.testing.refAllDecls(@This()); |
| 157 | 169 | } |
| 170 | ||
| 171 | pub fn FieldType(comptime T: type, comptime field: FieldEnum(T)) type { | |
| 172 | if (@typeInfo(T) != .@"struct" and @typeInfo(T) != .@"union") { | |
| 173 | @compileError("Expected struct or union, found '" ++ @typeName(T) ++ "'"); | |
| 174 | } | |
| 175 | ||
| 176 | @setEvalBranchQuota(std.math.maxInt(u32)); // i'm so sorry mlugg | |
| 177 | return std.meta.fieldInfo(T, field).type; | |
| 178 | } | |
| 179 | ||
| 180 | /// Returns an enum with a variant named after each field of `T`. | |
| 181 | pub fn FieldEnum(comptime T: type) type { | |
| 182 | @setEvalBranchQuota(std.math.maxInt(u32)); // i'm so sorry mlugg | |
| 183 | return std.meta.FieldEnum(T); | |
| 184 | } |
src/core/pool.zig created+49| ... | ... | @@ -0,0 +1,49 @@ |
| 1 | /// A memory pool lets you reuse shared objects. When objects are released, | |
| 2 | /// their state is not reset. This allows you to use it with objects such | |
| 3 | /// as ArenaAllocator. | |
| 4 | fn ObjectPool(Item: type, static_count: usize, dynamic_limit: usize, deinitFunction: anytype) type { | |
| 5 | return struct { | |
| 6 | static_data: [static_count]Item, | |
| 7 | static_usage: std.StaticBitSet(static_count), | |
| 8 | dynamic_unused: if (have_dynamic_pool) LinkedList else void, | |
| 9 | dynamic_count: if (have_dynamic_limit) std.math.IntFittingRange(0, dynamic_limit) else void, | |
| 10 | ||
| 11 | const have_dynamic_pool = dynamic_limit > 0; | |
| 12 | const have_dynamic_limit = have_dynamic_pool and dynamic_limit < std.math.maxInt(usize); | |
| 13 | ||
| 14 | const LinkedList = std.SinglyLinkedList(Item); | |
| 15 | ||
| 16 | const empty: @This() = .{ | |
| 17 | .static_data = undefined, | |
| 18 | .static_usage = .initFull(), | |
| 19 | .dynamic_unused = .{}, | |
| 20 | }; | |
| 21 | ||
| 22 | pub fn get(pool: *@This(), allocator: std.mem.Allocator) !*Item { | |
| 23 | if (pool.static_usage.toggleFirstSet()) |index| { | |
| 24 | return &pool.static_data[index]; | |
| 25 | } | |
| 26 | if (have_dynamic_pool) { | |
| 27 | if (pool.dynamic_unused.popFirst()) |first| { | |
| 28 | if (have_dynamic_limit) | |
| 29 | pool.dynamic_count -= 1; | |
| 30 | return &first.data; | |
| 31 | } | |
| 32 | } | |
| 33 | return &(try allocator.create(LinkedList.Node)).data; | |
| 34 | } | |
| 35 | ||
| 36 | pub fn release(pool: *@This(), item: *Item, allocator: std.mem.Allocator) void { | |
| 37 | deinitFunction(item); | |
| 38 | _ = pool; // autofix | |
| 39 | _ = allocator; // autofix | |
| 40 | // | |
| 41 | } | |
| 42 | }; | |
| 43 | } | |
| 44 | ||
| 45 | fn AutoObjectPool(Item: type) type { | |
| 46 | return ObjectPool(Item, 32, 32, Item.deinit); | |
| 47 | } | |
| 48 | ||
| 49 | const std = @import("std"); |
src/core/queue.zig created+47| ... | ... | @@ -0,0 +1,47 @@ |
| 1 | const std = @import("std"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const Allocator = std.mem.Allocator; | |
| 4 | ||
| 5 | /// A thread-safe queue where any number of threads may consume items, | |
| 6 | /// and any of those same threads may concurrently append new items. | |
| 7 | /// | |
| 8 | /// This relevant for multi-threaded compilation, where new tasks should | |
| 9 | /// be picked up by new threads | |
| 10 | pub fn Queue(comptime Item: type) type { | |
| 11 | return struct { | |
| 12 | const Node = std.SinglyLinkedList(Item).Node; | |
| 13 | ||
| 14 | head: ?*Node = null, | |
| 15 | tail: ?*Node = null, | |
| 16 | ||
| 17 | object_pool: std.SinglyLinkedList(Item) = .{}, | |
| 18 | ||
| 19 | pub fn init(thread_count: usize) @This() { | |
| 20 | assert(thread_count != 0); | |
| 21 | if (thread_count != 1) | |
| 22 | @panic("Multi-threaded queue is not yet implemented"); | |
| 23 | return .{}; | |
| 24 | } | |
| 25 | ||
| 26 | pub fn push(queue: *@This(), allocator: Allocator, item: Item) !void { | |
| 27 | const new_node = queue.object_pool.popFirst() orelse | |
| 28 | try allocator.create(Node); | |
| 29 | ||
| 30 | new_node.* = .{ .data = item }; | |
| 31 | if (queue.tail) |tail| { | |
| 32 | tail.next = new_node; | |
| 33 | } else { | |
| 34 | queue.head = new_node; | |
| 35 | } | |
| 36 | queue.tail = new_node; | |
| 37 | } | |
| 38 | ||
| 39 | pub fn next(queue: *@This()) ?Item { | |
| 40 | const head = queue.head orelse return null; | |
| 41 | queue.head = head.next; | |
| 42 | if (queue.head == null) queue.tail = null; | |
| 43 | queue.object_pool.prepend(head); | |
| 44 | return head.data; | |
| 45 | } | |
| 46 | }; | |
| 47 | } |
src/core/stack.zig+7-3| ... | ... | @@ -9,14 +9,16 @@ pub fn Stack(comptime Base: type) type { |
| 9 | 9 | return struct { |
| 10 | 10 | const Self = @This(); |
| 11 | 11 | |
| 12 | list: std.ArrayListUnmanaged(Base) = .{}, | |
| 12 | list: std.ArrayListUnmanaged(Base), | |
| 13 | ||
| 14 | pub const empty: Self = .{ .list = .empty }; | |
| 13 | 15 | |
| 14 | 16 | pub const Mark = struct { |
| 15 | 17 | stack: *Self, |
| 16 | 18 | index: usize, |
| 17 | 19 | |
| 18 | 20 | pub fn deinit(m: Mark) void { |
| 19 | if (std.debug.runtime_safety) | |
| 21 | if (core.runtime_safety) | |
| 20 | 22 | @memset(m.slice(Base), undefined); |
| 21 | 23 | m.stack.list.shrinkRetainingCapacity(m.index); |
| 22 | 24 | } |
| ... | ... | @@ -106,7 +108,7 @@ pub fn Stack(comptime Base: type) type { |
| 106 | 108 | return @field(types, @tagName(field)); |
| 107 | 109 | } |
| 108 | 110 | |
| 109 | pub fn slice(r: @This(), comptime field: FieldEnum) []Type(field) { | |
| 111 | pub fn items(r: @This(), comptime field: FieldEnum) []Type(field) { | |
| 110 | 112 | const f = comptime std.meta.fieldIndex(Types, @tagName(field)).?; |
| 111 | 113 | return @ptrCast(r.stack.list.items[r.starts[f]..][0..r.lengths[f]]); |
| 112 | 114 | } |
| ... | ... | @@ -154,3 +156,5 @@ pub fn Stack(comptime Base: type) type { |
| 154 | 156 | const std = @import("std"); |
| 155 | 157 | const assert = std.debug.assert; |
| 156 | 158 | const Allocator = std.mem.Allocator; |
| 159 | ||
| 160 | const core = @import("./mod.zig"); |
src/core/transform_cli.zig created+85| ... | ... | @@ -0,0 +1,85 @@ |
| 1 | //! Minimal CLI via `zig build transform -- filepath` | |
| 2 | //! Use this only for working on Parse+Transform | |
| 3 | ||
| 4 | pub fn main() !u8 { | |
| 5 | try core.unicode_props.init(); | |
| 6 | ||
| 7 | const use_gpa = @import("builtin").mode == .Debug; | |
| 8 | var gpa = if (use_gpa) | |
| 9 | std.heap.GeneralPurposeAllocator(.{ | |
| 10 | .stack_trace_frames = 100, | |
| 11 | }){} | |
| 12 | else | |
| 13 | std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
| 14 | defer _ = if (use_gpa) gpa.deinit(); | |
| 15 | const alloc = gpa.allocator(); | |
| 16 | ||
| 17 | var args = try std.process.argsWithAllocator(alloc); | |
| 18 | defer args.deinit(); | |
| 19 | ||
| 20 | _ = args.skip(); | |
| 21 | const file_path = args.next() orelse { | |
| 22 | std.debug.print("Missing filepath argument\n", .{}); | |
| 23 | return 1; | |
| 24 | }; | |
| 25 | ||
| 26 | const out = core.OutStream.getStderr(); | |
| 27 | ||
| 28 | const code = try std.fs.cwd() | |
| 29 | .readFileAllocOptions(alloc, file_path, std.math.maxInt(u32), null, @alignOf(u8), 0); | |
| 30 | defer alloc.free(code); | |
| 31 | ||
| 32 | var err_data: core.Parse.AstError.WithTrace = undefined; | |
| 33 | var ast = core.Parse.parse( | |
| 34 | alloc, | |
| 35 | code, | |
| 36 | &err_data, | |
| 37 | ) catch |err| switch (err) { | |
| 38 | error.OutOfMemory => return err, | |
| 39 | error.ParseFail => { | |
| 40 | var report = try err_data.toReport(alloc); | |
| 41 | defer report.deinit(alloc); | |
| 42 | ||
| 43 | report.dump( | |
| 44 | .{ .single_file = .{ | |
| 45 | .allocator = alloc, | |
| 46 | .url = file_path, | |
| 47 | .file = .{ .utf8_source = code }, | |
| 48 | } }, | |
| 49 | out, | |
| 50 | ) catch {}; | |
| 51 | ||
| 52 | return 1; | |
| 53 | }, | |
| 54 | }; | |
| 55 | defer ast.deinit(alloc); | |
| 56 | ||
| 57 | var teg_report: core.Report = undefined; | |
| 58 | var teg = core.Transform.transform(ast, alloc, &teg_report) catch |err| switch (err) { | |
| 59 | error.OutOfMemory => return err, | |
| 60 | error.TransformFail => { | |
| 61 | defer teg_report.deinit(alloc); | |
| 62 | teg_report.dump( | |
| 63 | .{ .single_file = .{ | |
| 64 | .allocator = alloc, | |
| 65 | .url = file_path, | |
| 66 | .file = .{ .ast = &ast }, | |
| 67 | } }, | |
| 68 | out, | |
| 69 | ) catch {}; | |
| 70 | return 1; | |
| 71 | }, | |
| 72 | }; | |
| 73 | defer teg.deinit(alloc); | |
| 74 | ||
| 75 | // const file = try std.fs.cwd().createFile("serialized.teg", .{}); | |
| 76 | // defer file.close(); | |
| 77 | // try teg.serialize(.{ .file = file }); | |
| 78 | ||
| 79 | try teg.dumpToWriter(out.writer, out.tty); | |
| 80 | ||
| 81 | return 0; | |
| 82 | } | |
| 83 | ||
| 84 | const std = @import("std"); | |
| 85 | const core = @import("core"); |
src/core/unicode_props.zig+1-1| ... | ... | @@ -23,7 +23,7 @@ pub fn init() !void { |
| 23 | 23 | if (allocation_size == 0) { |
| 24 | 24 | var logging_alloc = std.heap.loggingAllocator(std.heap.page_allocator); |
| 25 | 25 | props = try PropsData.init(logging_alloc.allocator()); |
| 26 | std.log.info("please write the above numbers into unicode.zig to prealloc this", .{}); | |
| 26 | std.log.err("please write the above numbers into unicode.zig to prealloc this", .{}); | |
| 27 | 27 | std.process.exit(1); |
| 28 | 28 | } |
| 29 | 29 |
src/deps/quickjs/build.zig+1-1| ... | ... | @@ -146,7 +146,7 @@ pub fn build(b: *Build) !void { |
| 146 | 146 | .optimize = optimize, |
| 147 | 147 | .link_libc = true, |
| 148 | 148 | }); |
| 149 | const module2 = translate_libc.addModule("quickjs_libc"); | |
| 149 | const module2 = translate_libc.addModule("quickjs-libc"); | |
| 150 | 150 | if (pthread.pthreads) |pt| translate_libc.addIncludeDir(pt.include.getPath(b)); |
| 151 | 151 | module2.linkLibrary(libquickjs); |
| 152 | 152 | module2.linkLibrary(libquickjs_libc); |
src/format/Format.zig created+84| ... | ... | @@ -0,0 +1,84 @@ |
| 1 | //! `Format` converts a parsed Ast back into source code. This uses | |
| 2 | //! the fact that `Ast` contains the original source code slice in | |
| 3 | //! order to preserve comments despite not representing them as nodes. | |
| 4 | ast: Ast, | |
| 5 | writer: AnyWriter, | |
| 6 | ident: usize = 0, | |
| 7 | ||
| 8 | // Formatting options such as tab size or character are intentionally | |
| 9 | // not exposed as configuration options. This makes all source files | |
| 10 | // follow the same style guide. | |
| 11 | const spaces_per_level = 3; | |
| 12 | ||
| 13 | pub fn format(ast: Ast, writer: AnyWriter) !void { | |
| 14 | var state: Format = .{ .ast = ast, .writer = writer }; | |
| 15 | ||
| 16 | // Print the top-level documentation comment | |
| 17 | { | |
| 18 | var i: u32 = 0; | |
| 19 | while (state.ast.tokenTag(i) == .module_doc_comment) { | |
| 20 | i += 1; | |
| 21 | } | |
| 22 | if (i > 0) { | |
| 23 | const start = state.ast.tokenOffset(0); | |
| 24 | const end = (mem.lastIndexOf( | |
| 25 | u8, | |
| 26 | state.ast.utf8_source[0..state.ast.tokenOffset(i)], | |
| 27 | "\n", | |
| 28 | ) orelse unreachable) + 1; | |
| 29 | try state.writer.writeAll(state.ast.utf8_source[start..end]); | |
| 30 | } | |
| 31 | } | |
| 32 | ||
| 33 | try state.formatNode(0); | |
| 34 | ||
| 35 | assert(state.ident == 0); | |
| 36 | } | |
| 37 | ||
| 38 | fn formatNode(state: Format, node: NodeIndex) !void { | |
| 39 | switch (state.ast.nodeTag(node)) { | |
| 40 | .root => { | |
| 41 | for (state.ast.nodeData(node).list.slice(&state.ast)) |child| { | |
| 42 | try state.formatNode(child); | |
| 43 | } | |
| 44 | }, | |
| 45 | .fn_decl => { | |
| 46 | const decl = state.ast.fullFnDecl(node); | |
| 47 | ||
| 48 | if (decl.public) { | |
| 49 | try state.writer.writeAll("pub "); | |
| 50 | } | |
| 51 | try state.writer.writeAll("fn "); | |
| 52 | try state.rewriteIdentifier(decl.id); | |
| 53 | try state.writer.writeAll("("); | |
| 54 | try state.writer.writeAll(")"); | |
| 55 | ||
| 56 | if (decl.return_type) |return_type| { | |
| 57 | try state.writer.writeAll(" -> "); | |
| 58 | try state.formatNode(return_type); | |
| 59 | } | |
| 60 | ||
| 61 | try state.writer.writeAll(" "); | |
| 62 | ||
| 63 | try state.formatNode(decl.body); | |
| 64 | ||
| 65 | try state.writer.writeAll("\n"); | |
| 66 | }, | |
| 67 | else => |unhandled_tag| std.debug.panic("TODO: unhandled tag .{s}", .{@tagName(unhandled_tag)}), | |
| 68 | } | |
| 69 | } | |
| 70 | ||
| 71 | fn rewriteIdentifier(state: Format, id: []const u8) !void { | |
| 72 | try state.writer.writeAll(id); | |
| 73 | } | |
| 74 | ||
| 75 | const Format = @This(); | |
| 76 | ||
| 77 | const std = @import("std"); | |
| 78 | const mem = std.mem; | |
| 79 | const assert = std.debug.assert; | |
| 80 | const AnyWriter = std.io.AnyWriter; | |
| 81 | ||
| 82 | const core = @import("core"); | |
| 83 | const Ast = core.Ast; | |
| 84 | const NodeIndex = Ast.NodeIndex; |
src/format/format_cli.zig created+63| ... | ... | @@ -0,0 +1,63 @@ |
| 1 | //! Minimal formatter CLI via `zig build format -- filepath` | |
| 2 | //! Use this only for working on the formatter. | |
| 3 | ||
| 4 | pub fn main() !u8 { | |
| 5 | try core.unicode_props.init(); | |
| 6 | ||
| 7 | const use_gpa = @import("builtin").mode == .Debug; | |
| 8 | var gpa = if (use_gpa) | |
| 9 | std.heap.GeneralPurposeAllocator(.{ | |
| 10 | .stack_trace_frames = 100, | |
| 11 | }){} | |
| 12 | else | |
| 13 | std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
| 14 | defer _ = if (use_gpa) gpa.deinit(); | |
| 15 | const alloc = gpa.allocator(); | |
| 16 | ||
| 17 | var args = try std.process.argsWithAllocator(alloc); | |
| 18 | defer args.deinit(); | |
| 19 | ||
| 20 | _ = args.skip(); | |
| 21 | const file_path = args.next() orelse { | |
| 22 | std.debug.print("Missing filepath argument\n", .{}); | |
| 23 | return 1; | |
| 24 | }; | |
| 25 | ||
| 26 | const code = try std.fs.cwd() | |
| 27 | .readFileAllocOptions(alloc, file_path, std.math.maxInt(u32), null, @alignOf(u8), 0); | |
| 28 | defer alloc.free(code); | |
| 29 | ||
| 30 | var err_data: core.Parse.AstError.WithTrace = undefined; | |
| 31 | var ast = core.Parse.parse( | |
| 32 | alloc, | |
| 33 | code, | |
| 34 | &err_data, | |
| 35 | ) catch |err| switch (err) { | |
| 36 | error.OutOfMemory => return err, | |
| 37 | error.ParseFail => { | |
| 38 | var report = try err_data.toReport(alloc); | |
| 39 | defer report.deinit(alloc); | |
| 40 | ||
| 41 | report.dump( | |
| 42 | .{ .single_file = .{ | |
| 43 | .allocator = alloc, | |
| 44 | .url = file_path, | |
| 45 | .file = .{ .utf8_source = code }, | |
| 46 | } }, | |
| 47 | .getStderr(), | |
| 48 | ) catch {}; | |
| 49 | ||
| 50 | return err; | |
| 51 | }, | |
| 52 | }; | |
| 53 | defer ast.deinit(alloc); | |
| 54 | ||
| 55 | const stdout = std.io.getStdOut(); | |
| 56 | try Format.format(ast, stdout.writer().any()); | |
| 57 | ||
| 58 | return 0; | |
| 59 | } | |
| 60 | ||
| 61 | const std = @import("std"); | |
| 62 | const core = @import("core"); | |
| 63 | const Format = @import("format"); |
src/loader/Loader.zig+2-4| ... | ... | @@ -252,7 +252,7 @@ pub const Failure = struct { |
| 252 | 252 | std.posix.PollError || |
| 253 | 253 | error{ |
| 254 | 254 | SchemeNotSupported, |
| 255 | /// `stat` lied | |
| 255 | /// The system `stat` has lied or otherwise file corruption | |
| 256 | 256 | UnexpectedEndOfFile, |
| 257 | 257 | /// non-200 status codes are errors |
| 258 | 258 | HttpStatus, |
| ... | ... | @@ -1032,9 +1032,7 @@ fn tickDownload( |
| 1032 | 1032 | |
| 1033 | 1033 | fn writeToCache(cache: std.fs.Dir, data: Module.Data) !void { |
| 1034 | 1034 | var rng: [16]u8 = undefined; |
| 1035 | std.posix.getrandom(&rng) catch { | |
| 1036 | rng = std.mem.asBytes(&std.time.nanoTimestamp()).*; | |
| 1037 | }; | |
| 1035 | std.crypto.random.bytes(&rng); | |
| 1038 | 1036 | |
| 1039 | 1037 | // TODO: i do not trust the atomicy of this on windows |
| 1040 | 1038 | // i think renameat will not use directory handles, when that is totally possible |
src/manual/generate-man.zig+1-1| ... | ... | @@ -12,7 +12,7 @@ const ArrayListUnmanaged = std.ArrayListUnmanaged; |
| 12 | 12 | const assert = std.debug.assert; |
| 13 | 13 | |
| 14 | 14 | comptime { |
| 15 | assert(std.debug.runtime_safety); | |
| 15 | assert(core.runtime_safety); | |
| 16 | 16 | } |
| 17 | 17 | |
| 18 | 18 | pub fn main() !void { |
src/wasm/enum_codegen.zig+10-11| ... | ... | @@ -1,9 +1,3 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Tokenizer = @import("Tokenizer.zig"); | |
| 3 | const Ast = @import("Ast.zig"); | |
| 4 | const Teg = @import("Teg.zig"); | |
| 5 | // const InternPool = @import("InternPool.zig"); | |
| 6 | ||
| 7 | 1 | pub fn main() !void { |
| 8 | 2 | var buf = std.io.bufferedWriter(std.io.getStdOut().writer()); |
| 9 | 3 | defer buf.flush() catch {}; |
| ... | ... | @@ -12,17 +6,22 @@ pub fn main() !void { |
| 12 | 6 | inline for (.{ |
| 13 | 7 | .{ "TokenTag", Tokenizer.Token.Tag }, |
| 14 | 8 | .{ "NodeTag", Ast.Node.Tag }, |
| 15 | .{ "TegInstTag", Teg.Inst.Tag }, | |
| 9 | .{ "TegInstTag", Teg.Tag }, | |
| 16 | 10 | .{ "TegInstRef", Teg.Inst.Ref }, |
| 17 | // .{ "InternPoolIndex", InternPool.Index }, | |
| 18 | 11 | }) |decl| { |
| 19 | 12 | const name, const enum_type = decl; |
| 20 | 13 | |
| 21 | try w.print("export const {s} = {{\n", .{name}); | |
| 14 | try w.print("pub enum {s} {{\n", .{name}); | |
| 22 | 15 | for (std.enums.values(enum_type)) |tag| { |
| 23 | try w.print(" {d}: \"{s}\",\n", .{ @intFromEnum(tag), @tagName(tag) }); | |
| 16 | try w.print(" @\"{s}\" = {d},\n", .{ @tagName(tag), @intFromEnum(tag) }); | |
| 24 | 17 | } |
| 25 | try w.writeAll("};\n"); | |
| 18 | try w.writeAll("}\n"); | |
| 26 | 19 | try w.writeAll("\n"); |
| 27 | 20 | } |
| 28 | 21 | } |
| 22 | ||
| 23 | const std = @import("std"); | |
| 24 | const core = @import("core"); | |
| 25 | const Tokenizer = core.Tokenizer; | |
| 26 | const Ast = core.Ast; | |
| 27 | const Teg = core.Teg; |
src/wasm/wasm_api.zig+7-8| ... | ... | @@ -10,13 +10,11 @@ export fn iterateTokens( |
| 10 | 10 | source_bytes: [*]const u8, |
| 11 | 11 | source_len: u32, |
| 12 | 12 | ) callconv(.C) i32 { |
| 13 | unicode_props.init() catch @panic("unicode_props.init failed"); | |
| 13 | core.unicode_props.init() catch @panic("unicode_props.init failed"); | |
| 14 | 14 | |
| 15 | var tokenizer: Tokenizer = .{ .code = source_bytes[0..source_len], .pos = 0 }; | |
| 15 | var tokenizer: Tokenizer = .{ .code = source_bytes[0..source_len :0], .pos = 0 }; | |
| 16 | 16 | tokenizer.skipWhitespace(); |
| 17 | while (tokenizer.next() catch { | |
| 18 | return @intCast(tokenizer.pos); | |
| 19 | }) |token| { | |
| 17 | while (tokenizer.next()) |token| { | |
| 20 | 18 | iterateTokenCallback(@intFromEnum(token.tag), token.offset); |
| 21 | 19 | tokenizer.skipWhitespace(); |
| 22 | 20 | } |
| ... | ... | @@ -25,8 +23,9 @@ export fn iterateTokens( |
| 25 | 23 | } |
| 26 | 24 | |
| 27 | 25 | const std = @import("std"); |
| 28 | const wasm_allocator = std.heap.wasm_allocator; | |
| 29 | const Tokenizer = @import("Tokenizer.zig"); | |
| 26 | const core = @import("core"); | |
| 27 | const Tokenizer = core.Tokenizer; | |
| 30 | 28 | const Token = Tokenizer.Token; |
| 31 | 29 | const ByteOffset = Tokenizer.ByteOffset; |
| 32 | const unicode_props = @import("unicode_props.zig"); | |
| 30 | ||
| 31 | const wasm_allocator = std.heap.wasm_allocator; |
test/behavior.zig deleted-393| ... | ... | @@ -1,393 +0,0 @@ |
| 1 | //! This program executes behavior tests found in ./test/behavior. Additionally, it can be run | |
| 2 | //! as a standalone program to compile and execute a single behavior test (for debugging) | |
| 3 | ||
| 4 | /// This file is included for all behavior tests for the JavaScript backend. | |
| 5 | const helpers = | |
| 6 | \\(function (self) { | |
| 7 | \\ function assert(condition) { | |
| 8 | \\ if (!condition) { | |
| 9 | \\ throw new Error("Assertion failure"); | |
| 10 | \\ } | |
| 11 | \\ } | |
| 12 | \\ | |
| 13 | \\ function assertEqual(actual, expected) { | |
| 14 | \\ if (actual !== expected) { | |
| 15 | \\ var err; | |
| 16 | \\ try { | |
| 17 | \\ err = new Error("Expected " + expected + ", but got " + actual); | |
| 18 | \\ } catch (e) { | |
| 19 | \\ err = new Error("Assertion failure"); | |
| 20 | \\ } | |
| 21 | \\ throw err; | |
| 22 | \\ } | |
| 23 | \\ } | |
| 24 | \\ | |
| 25 | \\ function print(str) { | |
| 26 | \\ console.log(str); | |
| 27 | \\ } | |
| 28 | \\ | |
| 29 | \\ self.assert = assert; | |
| 30 | \\ self.assertEqual = assertEqual; | |
| 31 | \\ self.print = print; | |
| 32 | \\})(typeof globalThis !== "undefined" ? globalThis : this); | |
| 33 | \\ | |
| 34 | ; | |
| 35 | ||
| 36 | const CaseOptions = struct { | |
| 37 | dump_ir: bool = false, | |
| 38 | sync: bool = false, | |
| 39 | }; | |
| 40 | ||
| 41 | const Case = struct { | |
| 42 | path: []const u8, | |
| 43 | display: []const u8, | |
| 44 | success: bool = false, | |
| 45 | logs: []const u8 = "", | |
| 46 | failure: ?struct { | |
| 47 | err: anyerror, | |
| 48 | stack: core.StoredTrace, | |
| 49 | } = null, | |
| 50 | }; | |
| 51 | ||
| 52 | fn runTestCase( | |
| 53 | backing_allocator: std.mem.Allocator, | |
| 54 | file_path: []const u8, | |
| 55 | case_options: CaseOptions, | |
| 56 | out: std.io.AnyWriter, | |
| 57 | ) !void { | |
| 58 | var gpa = std.heap.GeneralPurposeAllocator(.{ | |
| 59 | .stack_trace_frames = 100, | |
| 60 | }){ .backing_allocator = backing_allocator }; | |
| 61 | const alloc = gpa.allocator(); | |
| 62 | ||
| 63 | var backend = core.Js{}; | |
| 64 | var comp: core.Comp = .{ | |
| 65 | .gpa = tracer.tracingAllocator(&alloc), | |
| 66 | .options = .{ .dump_ir = case_options.dump_ir }, | |
| 67 | .backend = .{ .js = &backend }, | |
| 68 | .out = .{ .writer = out, .tty = .escape_codes }, | |
| 69 | }; | |
| 70 | try comp.init(); | |
| 71 | ||
| 72 | const main_file = try comp.getFile(file_path); | |
| 73 | ||
| 74 | var fail_report: core.Report = undefined; | |
| 75 | const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) { | |
| 76 | error.TransformFail => { | |
| 77 | try out.print("Transform failed:\n", .{}); | |
| 78 | try fail_report.dumpToWriter(&comp, out, .escape_codes); | |
| 79 | return error.TransformFail; | |
| 80 | }, | |
| 81 | error.ParseFail => { | |
| 82 | // Already printed. | |
| 83 | return error.ParseFail; | |
| 84 | }, | |
| 85 | else => return err, | |
| 86 | }; | |
| 87 | ||
| 88 | if (case_options.dump_ir) { | |
| 89 | try teg.dumpToWriter(out, .escape_codes); | |
| 90 | } | |
| 91 | ||
| 92 | const entry_point = try comp.semaEntryPoint(main_file); | |
| 93 | ||
| 94 | if (comp.temp_flag_had_errors) | |
| 95 | return error.SemaErrors; | |
| 96 | ||
| 97 | if (teg.report.warnings.len > 0) | |
| 98 | return error.TegWarnings; | |
| 99 | ||
| 100 | var js_out: std.ArrayListUnmanaged(u8) = .{}; | |
| 101 | defer js_out.deinit(alloc); | |
| 102 | ||
| 103 | try js_out.appendSlice(alloc, helpers); | |
| 104 | try js_out.appendSlice(alloc, "\n\n(function() {\n\n"); | |
| 105 | ||
| 106 | const start_i = js_out.items.len; | |
| 107 | try backend.link(&comp, entry_point, js_out.writer(alloc).any()); | |
| 108 | ||
| 109 | try out.writeAll("--- JS CODE ---\n"); | |
| 110 | try out.writeAll(js_out.items[start_i..]); | |
| 111 | try out.writeAll("--- JS CODE ---\n"); | |
| 112 | ||
| 113 | try js_out.appendSlice(alloc, "\n\n})();"); | |
| 114 | try js_out.append(alloc, 0); | |
| 115 | ||
| 116 | const runtime = qjs.JS_NewRuntime() orelse | |
| 117 | return error.UnexpectedError; | |
| 118 | defer qjs.JS_FreeRuntime(runtime); | |
| 119 | const js_ctx = qjs.JS_NewContext(runtime) orelse | |
| 120 | return error.UnexpectedError; | |
| 121 | defer qjs.JS_FreeContext(js_ctx); | |
| 122 | ||
| 123 | const result = qjs.JS_Eval( | |
| 124 | js_ctx, | |
| 125 | js_out.items.ptr, | |
| 126 | js_out.items.len - 1, | |
| 127 | "test.js", | |
| 128 | qjs.JS_EVAL_TYPE_GLOBAL, | |
| 129 | ); | |
| 130 | defer qjs.JS_FreeValue(js_ctx, result); | |
| 131 | ||
| 132 | if (qjs.JS_IsException(result) != 0) { | |
| 133 | const exception = qjs.JS_GetException(js_ctx); | |
| 134 | defer qjs.JS_FreeValue(js_ctx, exception); | |
| 135 | ||
| 136 | const exception_str = qjs.JS_ToCString(js_ctx, exception); | |
| 137 | defer qjs.JS_FreeCString(js_ctx, exception_str); | |
| 138 | ||
| 139 | const slice = std.mem.span(exception_str); | |
| 140 | ||
| 141 | try out.print("Exception: {s}\n", .{slice}); | |
| 142 | ||
| 143 | return error.RuntimeFailed; | |
| 144 | } | |
| 145 | } | |
| 146 | ||
| 147 | const threadlocal_state = struct { | |
| 148 | threadlocal var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
| 149 | }; | |
| 150 | ||
| 151 | pub fn runTestCaseWrap( | |
| 152 | gpa: std.mem.Allocator, | |
| 153 | case: *Case, | |
| 154 | case_options: CaseOptions, | |
| 155 | progress: std.Progress.Node, | |
| 156 | ) void { | |
| 157 | const node = progress.start(case.display, 0); | |
| 158 | defer node.end(); | |
| 159 | ||
| 160 | const alloc = threadlocal_state.arena.allocator(); | |
| 161 | defer _ = threadlocal_state.arena.reset(.{ .retain_with_limit = std.mem.page_size * 8 }); | |
| 162 | ||
| 163 | var logs: std.ArrayListUnmanaged(u8) = .{}; | |
| 164 | defer logs.deinit(alloc); | |
| 165 | ||
| 166 | defer case.logs = gpa.dupe(u8, logs.items) catch | |
| 167 | "[logs failed to allocate]"; | |
| 168 | ||
| 169 | if (case_options.sync) std.debug.lockStdErr(); | |
| 170 | defer if (case_options.sync) std.debug.unlockStdErr(); | |
| 171 | ||
| 172 | runTestCase( | |
| 173 | alloc, | |
| 174 | case.path, | |
| 175 | case_options, | |
| 176 | if (case_options.sync) | |
| 177 | std.io.getStdErr().writer().any() | |
| 178 | else | |
| 179 | logs.writer(alloc).any(), | |
| 180 | ) catch |err| { | |
| 181 | const stack = switch (err) { | |
| 182 | error.TransformFail, | |
| 183 | error.ParseFail, | |
| 184 | error.TegWarnings, | |
| 185 | error.SemaErrors, | |
| 186 | error.AnalysisFail, | |
| 187 | => core.StoredTrace.empty, | |
| 188 | ||
| 189 | else => core.StoredTrace.from(@errorReturnTrace()), | |
| 190 | }; | |
| 191 | ||
| 192 | case.failure = .{ | |
| 193 | .err = err, | |
| 194 | .stack = stack, | |
| 195 | }; | |
| 196 | ||
| 197 | return; | |
| 198 | }; | |
| 199 | ||
| 200 | case.success = true; | |
| 201 | } | |
| 202 | ||
| 203 | pub fn main() !void { | |
| 204 | // unicode props is asserted to be initialized before core.Tokenizer is used | |
| 205 | // this will result in a static page allocation, which is never cleaned up. | |
| 206 | try core.unicode_props.init(); | |
| 207 | var gpa = std.heap.GeneralPurposeAllocator(.{ | |
| 208 | .stack_trace_frames = 100, | |
| 209 | .thread_safe = true, | |
| 210 | }){}; | |
| 211 | ||
| 212 | defer _ = gpa.deinit(); | |
| 213 | const alloc = gpa.allocator(); | |
| 214 | ||
| 215 | const args = try std.process.argsAlloc(alloc); | |
| 216 | defer std.process.argsFree(alloc, args); | |
| 217 | ||
| 218 | var test_root: []const u8 = "test/behavior"; | |
| 219 | var case_options: CaseOptions = .{}; | |
| 220 | var all_logs = false; | |
| 221 | var filter: ?[]const u8 = null; | |
| 222 | defer if (filter) |f| alloc.free(f); | |
| 223 | ||
| 224 | var argument_error = false; | |
| 225 | ||
| 226 | for (args[1..]) |full_arg| { | |
| 227 | if (std.mem.startsWith(u8, full_arg, "--")) { | |
| 228 | if (getFlag("--test-root", full_arg)) |flag| { | |
| 229 | test_root = flag; | |
| 230 | } else if (std.mem.eql(u8, "--dump-ir", full_arg)) { | |
| 231 | case_options.dump_ir = true; | |
| 232 | } else if (std.mem.eql(u8, "--all-logs", full_arg)) { | |
| 233 | all_logs = true; | |
| 234 | } else if (std.mem.eql(u8, "--sync", full_arg)) { | |
| 235 | case_options.sync = true; | |
| 236 | } else { | |
| 237 | std.debug.print("Unknown argument: {s}\n", .{full_arg}); | |
| 238 | argument_error = true; | |
| 239 | } | |
| 240 | } else { | |
| 241 | if (filter != null) { | |
| 242 | std.debug.print("Unknown positional argument: {s}\n", .{full_arg}); | |
| 243 | } else { | |
| 244 | filter = try std.mem.replaceOwned( | |
| 245 | u8, | |
| 246 | alloc, | |
| 247 | full_arg, | |
| 248 | if (std.fs.path.sep == '/') "\\" else '/', | |
| 249 | std.fs.path.sep_str, | |
| 250 | ); | |
| 251 | } | |
| 252 | } | |
| 253 | } | |
| 254 | ||
| 255 | if (argument_error) std.process.exit(1); | |
| 256 | ||
| 257 | const root = try std.fs.cwd().openDir(test_root, .{ .iterate = true }); | |
| 258 | ||
| 259 | var test_cases: std.ArrayListUnmanaged(Case) = .{}; | |
| 260 | defer test_cases.deinit(alloc); | |
| 261 | defer for (test_cases.items) |test_case| { | |
| 262 | alloc.free(test_case.path); | |
| 263 | }; | |
| 264 | ||
| 265 | var path_buf: std.BoundedArray(u8, std.fs.max_path_bytes) = .{}; | |
| 266 | path_buf.len = @intCast((try std.os.getFdPath(root.fd, &path_buf.buffer)).len); | |
| 267 | try iterateTestCases(alloc, root, path_buf.len, &path_buf, &test_cases, filter); | |
| 268 | ||
| 269 | if (test_cases.items.len == 0) { | |
| 270 | if (filter) |f| { | |
| 271 | std.debug.print("No test cases match filter \"{s}\"\n", .{f}); | |
| 272 | } else { | |
| 273 | std.debug.print("No test cases\n", .{}); | |
| 274 | } | |
| 275 | std.process.exit(1); | |
| 276 | } | |
| 277 | ||
| 278 | const progress = std.Progress.start(.{ .root_name = "Behavior Tests" }); | |
| 279 | defer progress.end(); | |
| 280 | progress.setEstimatedTotalItems(test_cases.items.len); | |
| 281 | ||
| 282 | var wg: std.Thread.WaitGroup = .{}; | |
| 283 | ||
| 284 | var pool: std.Thread.Pool = undefined; | |
| 285 | if (!case_options.sync) try pool.init(.{ .allocator = alloc }); | |
| 286 | defer if (!case_options.sync) pool.deinit(); | |
| 287 | ||
| 288 | for (test_cases.items) |*test_case| { | |
| 289 | const call_args = .{ | |
| 290 | alloc, | |
| 291 | test_case, | |
| 292 | case_options, | |
| 293 | progress, | |
| 294 | }; | |
| 295 | ||
| 296 | if (case_options.sync) { | |
| 297 | @call(.auto, runTestCaseWrap, call_args); | |
| 298 | } else { | |
| 299 | pool.spawnWg(&wg, runTestCaseWrap, call_args); | |
| 300 | } | |
| 301 | } | |
| 302 | ||
| 303 | wg.wait(); | |
| 304 | ||
| 305 | for (test_cases.items) |*case| { | |
| 306 | if (case.success) { | |
| 307 | if (all_logs) { | |
| 308 | std.debug.print("PASS: {s}\n", .{case.display}); | |
| 309 | std.debug.print("{s}", .{case.logs}); | |
| 310 | } | |
| 311 | } else if (case.failure) |*fail| { | |
| 312 | std.debug.print("FAIL: {s}\n", .{case.display}); | |
| 313 | std.debug.print("{s}", .{case.logs}); | |
| 314 | if (fail.stack.index > 0) { | |
| 315 | std.debug.print("Error return trace:\n", .{}); | |
| 316 | std.debug.dumpStackTrace(fail.stack.trace()); | |
| 317 | } | |
| 318 | } else { | |
| 319 | // success was never set true but failure was left null | |
| 320 | // this means runTestCaseWrap was never called | |
| 321 | std.debug.print("INVALID STATE: {s}\n", .{case.path}); | |
| 322 | } | |
| 323 | if (case.logs.len > 0) | |
| 324 | alloc.free(case.logs); | |
| 325 | } | |
| 326 | } | |
| 327 | ||
| 328 | fn iterateTestCases( | |
| 329 | alloc: std.mem.Allocator, | |
| 330 | root: Dir, | |
| 331 | root_path_len: usize, | |
| 332 | path_buf: *std.BoundedArray(u8, std.fs.max_path_bytes), | |
| 333 | test_cases: *std.ArrayListUnmanaged(Case), | |
| 334 | filter: ?[]const u8, | |
| 335 | ) !void { | |
| 336 | var it = root.iterate(); | |
| 337 | while (try it.next()) |entry| { | |
| 338 | switch (entry.kind) { | |
| 339 | .file => { | |
| 340 | if (entry.name.len > 0 and entry.name[0] == '.') | |
| 341 | continue; | |
| 342 | ||
| 343 | const path = try std.fs.path.join(alloc, &.{ | |
| 344 | path_buf.slice(), | |
| 345 | entry.name, | |
| 346 | }); | |
| 347 | ||
| 348 | if (filter) |f| { | |
| 349 | if (std.mem.indexOf(u8, path, f) == null) { | |
| 350 | alloc.free(path); | |
| 351 | continue; | |
| 352 | } | |
| 353 | } | |
| 354 | ||
| 355 | try test_cases.append(alloc, .{ | |
| 356 | .path = path, | |
| 357 | .display = path[root_path_len + 1 ..], | |
| 358 | }); | |
| 359 | }, | |
| 360 | .directory => { | |
| 361 | const top = path_buf.len; | |
| 362 | defer path_buf.len = top; | |
| 363 | ||
| 364 | try path_buf.append(std.fs.path.sep); | |
| 365 | try path_buf.appendSlice(entry.name); | |
| 366 | ||
| 367 | const sub_dir = try root.openDir(entry.name, .{ .iterate = true }); | |
| 368 | try iterateTestCases(alloc, sub_dir, root_path_len, path_buf, test_cases, filter); | |
| 369 | }, | |
| 370 | else => return error.UnsupportedFileType, | |
| 371 | } | |
| 372 | } | |
| 373 | } | |
| 374 | ||
| 375 | fn getFlag(name: []const u8, full_arg: []const u8) ?[]const u8 { | |
| 376 | if (full_arg.len > name.len + 1 and | |
| 377 | full_arg[name.len] == '=' and | |
| 378 | std.mem.startsWith(u8, name, full_arg)) | |
| 379 | { | |
| 380 | return full_arg[name.len + 1 ..]; | |
| 381 | } | |
| 382 | ||
| 383 | return null; | |
| 384 | } | |
| 385 | ||
| 386 | const std = @import("std"); | |
| 387 | const Dir = std.fs.Dir; | |
| 388 | ||
| 389 | const core = @import("core"); | |
| 390 | const qjs = @import("quickjs"); | |
| 391 | ||
| 392 | const tracer = @import("tracer"); | |
| 393 | pub const tracer_impl = @field(tracer, @import("build_options").tracing); |
test/behavior/arithmetic+18-15| ... | ... | @@ -1,3 +1,21 @@ |
| 1 | let assertEqual = @extern("assertEqual"); | |
| 2 | ||
| 3 | fn main { | |
| 4 | assertEqual(addFloat(1, 2), f(3)); | |
| 5 | assertEqual(addFloat(1.5, 2.2), f(3.7)); | |
| 6 | assertEqual(subFloat(1, 2), f(-1)); | |
| 7 | assertEqual(subFloat(1.5, 2.2), f(-0.7000000000000002)); | |
| 8 | assertEqual(mulFloat(1, 2), f(2)); | |
| 9 | assertEqual(mulFloat(1.5, 2.2), f(3.3000000000000003)); | |
| 10 | assertEqual(divFloat(1, 2), f(0.5)); | |
| 11 | assertEqual(divFloat(1.5, 2.2), f(0.6818181818181818)); | |
| 12 | ||
| 13 | assertEqual(addInt(1, 2), i(3)); | |
| 14 | assertEqual(addInt(-421, 592), i(171)); | |
| 15 | assertEqual(subInt(1, 2), i(-1)); | |
| 16 | assertEqual(subInt(528, -43), i(571)); | |
| 17 | } | |
| 18 | ||
| 1 | 19 | fn f(a: float) -> float { a } |
| 2 | 20 | |
| 3 | 21 | fn addFloat(a: float, b: float) -> float { |
| ... | ... | @@ -50,18 +68,3 @@ fn subInt(a: int, b: int) -> int { |
| 50 | 68 | // a % b |
| 51 | 69 | // } |
| 52 | 70 | |
| 53 | let assertEqual = @js("assertEqual"); | |
| 54 | ||
| 55 | assertEqual(addFloat(1, 2), f(3)); | |
| 56 | assertEqual(addFloat(1.5, 2.2), f(3.7)); | |
| 57 | assertEqual(subFloat(1, 2), f(-1)); | |
| 58 | assertEqual(subFloat(1.5, 2.2), f(-0.7000000000000002)); | |
| 59 | assertEqual(mulFloat(1, 2), f(2)); | |
| 60 | assertEqual(mulFloat(1.5, 2.2), f(3.3000000000000003)); | |
| 61 | assertEqual(divFloat(1, 2), f(0.5)); | |
| 62 | assertEqual(divFloat(1.5, 2.2), f(0.6818181818181818)); | |
| 63 | ||
| 64 | assertEqual(addInt(1, 2), i(3)); | |
| 65 | assertEqual(addInt(-421, 592), i(171)); | |
| 66 | assertEqual(subInt(1, 2), i(-1)); | |
| 67 | assertEqual(subInt(528, -43), i(571)); |
test/behavior/array+11-9| ... | ... | @@ -1,12 +1,14 @@ |
| 1 | fn i(a: int) -> int { a } | |
| 1 | let assertEqual = @extern("assertEqual"); | |
| 2 | 2 | |
| 3 | let array = [i(9), i(3), i(8), i(3), i(-1)]; | |
| 3 | fn main { | |
| 4 | let array = [i(9), i(3), i(8), i(3), i(-1)]; | |
| 4 | 5 | |
| 5 | let assertEqual = @js("assertEqual"); | |
| 6 | assertEqual(array.len, i(5)); | |
| 7 | assertEqual(array[0], i(9)); | |
| 8 | assertEqual(array[1], i(3)); | |
| 9 | assertEqual(array.2, i(8)); | |
| 10 | assertEqual(array.3, i(3)); | |
| 11 | assertEqual(array[i(1) + 2] + array[i(2) + 2], i(2)); | |
| 12 | } | |
| 6 | 13 | |
| 7 | assertEqual(array.len, i(5)); | |
| 8 | assertEqual(array[0], i(9)); | |
| 9 | assertEqual(array[1], i(3)); | |
| 10 | assertEqual(array.2, i(8)); | |
| 11 | assertEqual(array.3, i(3)); | |
| 12 | assertEqual(array[i(1) + 2] + array[i(2) + 2], i(2)); | |
| 14 | fn i(a: int) -> int { a } |
test/behavior/empty_function created+2| ... | ... | @@ -0,0 +1,2 @@ |
| 1 | // Ensure that an empty file is indeed executable without failure. | |
| 2 | fn main {} |
test/behavior/function created+17| ... | ... | @@ -0,0 +1,17 @@ |
| 1 | let assertEqual = @extern("assertEqual"); | |
| 2 | ||
| 3 | fn i(x: int) -> int { x } | |
| 4 | ||
| 5 | fn hello(n: int) -> int { | |
| 6 | if n > 1 { | |
| 7 | hello(n - 1) + 1 | |
| 8 | } else { | |
| 9 | // TODO: fix a missing coercion | |
| 10 | let i: int = 1; | |
| 11 | i | |
| 12 | } | |
| 13 | } | |
| 14 | ||
| 15 | fn main { | |
| 16 | assertEqual(hello(5), i(5)); | |
| 17 | } |
test/compiler.zig created+431| ... | ... | @@ -0,0 +1,431 @@ |
| 1 | //! This program executes compiler tests found in this directory. Additionally, | |
| 2 | //! it can be run as a standalone program to compile and execute a single test. | |
| 3 | //! | |
| 4 | //! See the "Testing" section in `architecture.md` for more information on the | |
| 5 | //! structure of these tests, and `zig build test-compiler -- --help` for more | |
| 6 | //! details on the arguments this supports. | |
| 7 | ||
| 8 | /// This file is included for all behavior tests for the JavaScript backend. | |
| 9 | const helpers = | |
| 10 | \\(function (self) { | |
| 11 | \\ function assert(condition) { | |
| 12 | \\ if (!condition) { | |
| 13 | \\ throw new Error("Assertion failure"); | |
| 14 | \\ } | |
| 15 | \\ } | |
| 16 | \\ | |
| 17 | \\ function assertEqual(actual, expected) { | |
| 18 | \\ if (actual !== expected) { | |
| 19 | \\ var err; | |
| 20 | \\ try { | |
| 21 | \\ err = new Error("Expected " + expected + ", but got " + actual); | |
| 22 | \\ } catch (e) { | |
| 23 | \\ err = new Error("Assertion failure"); | |
| 24 | \\ } | |
| 25 | \\ throw err; | |
| 26 | \\ } | |
| 27 | \\ } | |
| 28 | \\ | |
| 29 | \\ function print(str) { | |
| 30 | \\ console.log(str); | |
| 31 | \\ } | |
| 32 | \\ | |
| 33 | \\ self.assert = assert; | |
| 34 | \\ self.assertEqual = assertEqual; | |
| 35 | \\ self.print = print; | |
| 36 | \\})(typeof globalThis !== "undefined" ? globalThis : this); | |
| 37 | \\ | |
| 38 | ; | |
| 39 | ||
| 40 | const CaseOptions = struct { | |
| 41 | dump_ir: bool = false, | |
| 42 | sync: bool = false, | |
| 43 | }; | |
| 44 | ||
| 45 | const Case = struct { | |
| 46 | path: []const u8, | |
| 47 | display: []const u8, | |
| 48 | success: bool = false, | |
| 49 | logs: []const u8 = "", | |
| 50 | failure: ?struct { | |
| 51 | err: anyerror, | |
| 52 | stack: core.StoredTrace, | |
| 53 | } = null, | |
| 54 | ||
| 55 | const Kind = enum { | |
| 56 | behavior, | |
| 57 | analysis, | |
| 58 | transform_error, | |
| 59 | }; | |
| 60 | }; | |
| 61 | ||
| 62 | fn runTestCase( | |
| 63 | backing_allocator: std.mem.Allocator, | |
| 64 | file_path: []const u8, | |
| 65 | case_options: CaseOptions, | |
| 66 | out: std.io.AnyWriter, | |
| 67 | ) !void { | |
| 68 | var gpa = std.heap.GeneralPurposeAllocator(.{ | |
| 69 | .stack_trace_frames = 100, | |
| 70 | }){ .backing_allocator = backing_allocator }; | |
| 71 | const alloc = gpa.allocator(); | |
| 72 | ||
| 73 | var backend = core.Js{}; | |
| 74 | var comp = try core.Comp.init(.{ | |
| 75 | .gpa = tracer.tracingAllocator(&alloc), | |
| 76 | .options = .{ .dump_ir = case_options.dump_ir }, | |
| 77 | .backend = .{ .js = &backend }, | |
| 78 | .out = .{ .writer = out, .tty = .escape_codes }, | |
| 79 | }); | |
| 80 | ||
| 81 | const main_file = try comp.getFile(file_path); | |
| 82 | ||
| 83 | var fail_report: core.Report = undefined; | |
| 84 | const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) { | |
| 85 | error.TransformFail => { | |
| 86 | try out.print("Transform failed:\n", .{}); | |
| 87 | try fail_report.dump(.{ .comp = .{ .comp = &comp, .file = main_file } }, comp.out); | |
| 88 | return error.TransformFail; | |
| 89 | }, | |
| 90 | error.ParseFail => { | |
| 91 | // Already printed. | |
| 92 | return error.ParseFail; | |
| 93 | }, | |
| 94 | else => return err, | |
| 95 | }; | |
| 96 | ||
| 97 | if (case_options.dump_ir) { | |
| 98 | try teg.dumpToWriter(out, .escape_codes); | |
| 99 | } | |
| 100 | ||
| 101 | const entry_point = try comp.enqueueEntryModule(main_file); | |
| 102 | try comp.runAllJobs(); | |
| 103 | ||
| 104 | if (comp.temp_flag_had_errors) | |
| 105 | return error.SemaErrors; | |
| 106 | ||
| 107 | if (teg.report.warnings.len > 0) | |
| 108 | return error.TegWarnings; | |
| 109 | ||
| 110 | var js_out: std.ArrayListUnmanaged(u8) = .{}; | |
| 111 | defer js_out.deinit(alloc); | |
| 112 | ||
| 113 | try js_out.appendSlice(alloc, helpers); | |
| 114 | try js_out.appendSlice(alloc, "\n\n(function() {\n\n"); | |
| 115 | ||
| 116 | const start_i = js_out.items.len; | |
| 117 | try backend.link(&comp, entry_point, js_out.writer(alloc).any()); | |
| 118 | ||
| 119 | try out.writeAll("--- JS CODE ---\n"); | |
| 120 | try out.writeAll(js_out.items[start_i..]); | |
| 121 | try out.writeAll("--- JS CODE ---\n"); | |
| 122 | ||
| 123 | if (isOnlyComments(js_out.items[start_i..])) { | |
| 124 | try out.writeAll("Behavior tests should output code!"); | |
| 125 | return error.EmptyFile; | |
| 126 | } | |
| 127 | ||
| 128 | try js_out.appendSlice(alloc, "\n\n})();"); | |
| 129 | try js_out.append(alloc, 0); | |
| 130 | ||
| 131 | const runtime = qjs.JS_NewRuntime() orelse | |
| 132 | return error.UnexpectedError; | |
| 133 | defer qjs.JS_FreeRuntime(runtime); | |
| 134 | const js_ctx = qjs.JS_NewContext(runtime) orelse | |
| 135 | return error.UnexpectedError; | |
| 136 | defer qjs.JS_FreeContext(js_ctx); | |
| 137 | ||
| 138 | const result = qjs.JS_Eval( | |
| 139 | js_ctx, | |
| 140 | js_out.items.ptr, | |
| 141 | js_out.items.len - 1, | |
| 142 | "test.js", | |
| 143 | qjs.JS_EVAL_TYPE_GLOBAL, | |
| 144 | ); | |
| 145 | defer qjs.JS_FreeValue(js_ctx, result); | |
| 146 | ||
| 147 | if (qjs.JS_IsException(result) != 0) { | |
| 148 | const exception = qjs.JS_GetException(js_ctx); | |
| 149 | defer qjs.JS_FreeValue(js_ctx, exception); | |
| 150 | ||
| 151 | const exception_str = qjs.JS_ToCString(js_ctx, exception); | |
| 152 | defer qjs.JS_FreeCString(js_ctx, exception_str); | |
| 153 | ||
| 154 | const slice = std.mem.span(exception_str); | |
| 155 | ||
| 156 | try out.print("Exception: {s}\n", .{slice}); | |
| 157 | ||
| 158 | return error.RuntimeFailed; | |
| 159 | } | |
| 160 | } | |
| 161 | ||
| 162 | fn isOnlyComments(input: []const u8) bool { | |
| 163 | var rest = input; | |
| 164 | while (rest.len > 0) { | |
| 165 | if (rest.len == 1) return false; | |
| 166 | if (rest[0] == '/' and rest[1] == '/') { | |
| 167 | rest = rest[std.mem.indexOfScalar(u8, rest, '\n') orelse return true ..]; | |
| 168 | } else { | |
| 169 | return false; | |
| 170 | } | |
| 171 | } | |
| 172 | return true; | |
| 173 | } | |
| 174 | ||
| 175 | const threadlocal_state = struct { | |
| 176 | threadlocal var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
| 177 | }; | |
| 178 | ||
| 179 | pub fn runTestCaseWrap( | |
| 180 | gpa: std.mem.Allocator, | |
| 181 | case: *Case, | |
| 182 | case_options: CaseOptions, | |
| 183 | progress: std.Progress.Node, | |
| 184 | ) void { | |
| 185 | const node = progress.start(case.display, 0); | |
| 186 | defer node.end(); | |
| 187 | ||
| 188 | const alloc = threadlocal_state.arena.allocator(); | |
| 189 | defer _ = threadlocal_state.arena.reset(.{ .retain_with_limit = std.mem.page_size * 8 }); | |
| 190 | ||
| 191 | var logs: std.ArrayListUnmanaged(u8) = .{}; | |
| 192 | defer logs.deinit(alloc); | |
| 193 | ||
| 194 | defer case.logs = gpa.dupe(u8, logs.items) catch | |
| 195 | "[logs failed to allocate]"; | |
| 196 | ||
| 197 | runTestCase( | |
| 198 | alloc, | |
| 199 | case.path, | |
| 200 | case_options, | |
| 201 | if (case_options.sync) | |
| 202 | std.io.getStdErr().writer().any() | |
| 203 | else | |
| 204 | logs.writer(alloc).any(), | |
| 205 | ) catch |err| { | |
| 206 | const stack = switch (err) { | |
| 207 | error.TransformFail, | |
| 208 | error.ParseFail, | |
| 209 | error.TegWarnings, | |
| 210 | error.SemaErrors, | |
| 211 | error.AnalysisFail, | |
| 212 | => core.StoredTrace.empty, | |
| 213 | ||
| 214 | else => core.StoredTrace.from(@errorReturnTrace()), | |
| 215 | }; | |
| 216 | ||
| 217 | case.failure = .{ | |
| 218 | .err = err, | |
| 219 | .stack = stack, | |
| 220 | }; | |
| 221 | ||
| 222 | return; | |
| 223 | }; | |
| 224 | ||
| 225 | case.success = true; | |
| 226 | } | |
| 227 | ||
| 228 | pub fn main() !void { | |
| 229 | // unicode props is asserted to be initialized before core.Tokenizer is used | |
| 230 | // this will result in a static page allocation, which is never cleaned up. | |
| 231 | try core.unicode_props.init(); | |
| 232 | var gpa = std.heap.GeneralPurposeAllocator(.{ | |
| 233 | .stack_trace_frames = 100, | |
| 234 | .thread_safe = true, | |
| 235 | }){}; | |
| 236 | ||
| 237 | defer _ = gpa.deinit(); | |
| 238 | const alloc = gpa.allocator(); | |
| 239 | ||
| 240 | const args = try std.process.argsAlloc(alloc); | |
| 241 | defer std.process.argsFree(alloc, args); | |
| 242 | ||
| 243 | var test_root: []const u8 = "test/behavior"; | |
| 244 | var case_options: CaseOptions = .{}; | |
| 245 | var all_logs = false; | |
| 246 | var filter: ?[]const u8 = null; | |
| 247 | defer if (filter) |f| alloc.free(f); | |
| 248 | ||
| 249 | var argument_error = false; | |
| 250 | ||
| 251 | for (args[1..]) |full_arg| { | |
| 252 | if (std.mem.startsWith(u8, full_arg, "--")) { | |
| 253 | if (getFlag("--test-root", full_arg)) |flag| { | |
| 254 | test_root = flag; | |
| 255 | } else if (std.mem.eql(u8, "--dump-ir", full_arg)) { | |
| 256 | case_options.dump_ir = true; | |
| 257 | } else if (std.mem.eql(u8, "--all-logs", full_arg)) { | |
| 258 | all_logs = true; | |
| 259 | } else if (std.mem.eql(u8, "--sync", full_arg)) { | |
| 260 | case_options.sync = true; | |
| 261 | } else { | |
| 262 | std.debug.print("Unknown argument: {s}\n", .{full_arg}); | |
| 263 | argument_error = true; | |
| 264 | } | |
| 265 | } else { | |
| 266 | if (filter != null) { | |
| 267 | std.debug.print("Unknown positional argument: {s}\n", .{full_arg}); | |
| 268 | } else { | |
| 269 | filter = try std.mem.replaceOwned( | |
| 270 | u8, | |
| 271 | alloc, | |
| 272 | full_arg, | |
| 273 | if (std.fs.path.sep == '/') "\\" else '/', | |
| 274 | std.fs.path.sep_str, | |
| 275 | ); | |
| 276 | } | |
| 277 | } | |
| 278 | } | |
| 279 | ||
| 280 | if (argument_error) { | |
| 281 | // TODO: print a nice help menu | |
| 282 | std.process.exit(1); | |
| 283 | } | |
| 284 | ||
| 285 | const some_logs = all_logs or args.len > 1; | |
| 286 | ||
| 287 | const root = try std.fs.cwd().openDir(test_root, .{ .iterate = true }); | |
| 288 | ||
| 289 | var test_cases: std.ArrayListUnmanaged(Case) = .{}; | |
| 290 | defer test_cases.deinit(alloc); | |
| 291 | defer for (test_cases.items) |test_case| { | |
| 292 | alloc.free(test_case.path); | |
| 293 | }; | |
| 294 | ||
| 295 | var path_buf: std.BoundedArray(u8, std.fs.max_path_bytes) = .{}; | |
| 296 | path_buf.len = @intCast((try std.os.getFdPath(root.fd, &path_buf.buffer)).len); | |
| 297 | try iterateTestCases(alloc, root, path_buf.len, &path_buf, &test_cases, filter); | |
| 298 | ||
| 299 | if (test_cases.items.len == 0) { | |
| 300 | if (filter) |f| { | |
| 301 | std.debug.print("No test cases match filter \"{s}\"\n", .{f}); | |
| 302 | } else { | |
| 303 | std.debug.print("No test cases\n", .{}); | |
| 304 | } | |
| 305 | std.process.exit(1); | |
| 306 | } | |
| 307 | ||
| 308 | const progress = std.Progress.start(.{ .root_name = "Behavior Tests" }); | |
| 309 | defer progress.end(); | |
| 310 | progress.setEstimatedTotalItems(test_cases.items.len); | |
| 311 | ||
| 312 | var wg: std.Thread.WaitGroup = .{}; | |
| 313 | ||
| 314 | var pool: std.Thread.Pool = undefined; | |
| 315 | if (!case_options.sync) try pool.init(.{ .allocator = alloc }); | |
| 316 | defer if (!case_options.sync) pool.deinit(); | |
| 317 | ||
| 318 | for (test_cases.items) |*test_case| { | |
| 319 | const call_args = .{ | |
| 320 | alloc, | |
| 321 | test_case, | |
| 322 | case_options, | |
| 323 | progress, | |
| 324 | }; | |
| 325 | ||
| 326 | if (case_options.sync) { | |
| 327 | @call(.auto, runTestCaseWrap, call_args); | |
| 328 | } else { | |
| 329 | pool.spawnWg(&wg, runTestCaseWrap, call_args); | |
| 330 | } | |
| 331 | } | |
| 332 | ||
| 333 | wg.wait(); | |
| 334 | ||
| 335 | var num_pass: usize = 0; | |
| 336 | for (test_cases.items) |*case| { | |
| 337 | if (case.success) { | |
| 338 | num_pass += 1; | |
| 339 | if (some_logs) { | |
| 340 | std.debug.print("PASS: {s}\n", .{case.display}); | |
| 341 | } | |
| 342 | if (all_logs) { | |
| 343 | std.debug.print("{s}", .{case.logs}); | |
| 344 | } | |
| 345 | } else if (case.failure) |*fail| { | |
| 346 | std.debug.print("FAIL: {s}\n", .{case.display}); | |
| 347 | std.debug.print("{s}", .{case.logs}); | |
| 348 | if (fail.stack.index > 0) { | |
| 349 | std.debug.print("Error return trace:\n", .{}); | |
| 350 | std.debug.dumpStackTrace(fail.stack.trace()); | |
| 351 | } | |
| 352 | } else { | |
| 353 | // success was never set true but failure was left null | |
| 354 | // this means runTestCaseWrap was never called | |
| 355 | std.debug.print("INVALID STATE: {s}\n", .{case.path}); | |
| 356 | } | |
| 357 | if (case.logs.len > 0) | |
| 358 | alloc.free(case.logs); | |
| 359 | } | |
| 360 | ||
| 361 | if (some_logs) { | |
| 362 | std.debug.print("Complete: {d} PASS / {d} TOTAL\n", .{ num_pass, test_cases.items.len }); | |
| 363 | } | |
| 364 | } | |
| 365 | ||
| 366 | fn iterateTestCases( | |
| 367 | alloc: std.mem.Allocator, | |
| 368 | root: Dir, | |
| 369 | root_path_len: usize, | |
| 370 | path_buf: *std.BoundedArray(u8, std.fs.max_path_bytes), | |
| 371 | test_cases: *std.ArrayListUnmanaged(Case), | |
| 372 | filter: ?[]const u8, | |
| 373 | ) !void { | |
| 374 | var it = root.iterate(); | |
| 375 | while (try it.next()) |entry| { | |
| 376 | switch (entry.kind) { | |
| 377 | .file => { | |
| 378 | if (entry.name.len > 0 and entry.name[0] == '.') | |
| 379 | continue; | |
| 380 | ||
| 381 | const path = try std.fs.path.join(alloc, &.{ | |
| 382 | path_buf.slice(), | |
| 383 | entry.name, | |
| 384 | }); | |
| 385 | ||
| 386 | if (filter) |f| { | |
| 387 | if (std.mem.indexOf(u8, path, f) == null) { | |
| 388 | alloc.free(path); | |
| 389 | continue; | |
| 390 | } | |
| 391 | } | |
| 392 | ||
| 393 | try test_cases.append(alloc, .{ | |
| 394 | .path = path, | |
| 395 | .display = path[root_path_len + 1 ..], | |
| 396 | }); | |
| 397 | }, | |
| 398 | .directory => { | |
| 399 | const top = path_buf.len; | |
| 400 | defer path_buf.len = top; | |
| 401 | ||
| 402 | try path_buf.append(std.fs.path.sep); | |
| 403 | try path_buf.appendSlice(entry.name); | |
| 404 | ||
| 405 | const sub_dir = try root.openDir(entry.name, .{ .iterate = true }); | |
| 406 | try iterateTestCases(alloc, sub_dir, root_path_len, path_buf, test_cases, filter); | |
| 407 | }, | |
| 408 | else => return error.UnsupportedFileType, | |
| 409 | } | |
| 410 | } | |
| 411 | } | |
| 412 | ||
| 413 | fn getFlag(name: []const u8, full_arg: []const u8) ?[]const u8 { | |
| 414 | if (full_arg.len > name.len + 1 and | |
| 415 | full_arg[name.len] == '=' and | |
| 416 | std.mem.startsWith(u8, name, full_arg)) | |
| 417 | { | |
| 418 | return full_arg[name.len + 1 ..]; | |
| 419 | } | |
| 420 | ||
| 421 | return null; | |
| 422 | } | |
| 423 | ||
| 424 | const std = @import("std"); | |
| 425 | const Dir = std.fs.Dir; | |
| 426 | ||
| 427 | const core = @import("core"); | |
| 428 | const qjs = @import("quickjs"); | |
| 429 | ||
| 430 | const tracer = @import("tracer"); | |
| 431 | pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)); |
test/parse_error/string_invalid_escape created+3| ... | ... | @@ -0,0 +1,3 @@ |
| 1 | let abc = "hello \j"; | |
| 2 | ||
| 3 | // error:1:11: expected an expression, but found unterminated string literal |
test/parse_error/string_unterminated created+5| ... | ... | @@ -0,0 +1,5 @@ |
| 1 | let abc = "hello | |
| 2 | let def = 123; | |
| 3 | let hij = "wtf | |
| 4 | ||
| 5 | // error:1:11: expected an expression, but found unterminated string literal |
test/transform_error/break_across_function created+8| ... | ... | @@ -0,0 +1,8 @@ |
| 1 | let x = hello: { | |
| 2 | fn() { | |
| 3 | break :hello 123; | |
| 4 | } | |
| 5 | }; | |
| 6 | _ = x; | |
| 7 | ||
| 8 | // error:3:15: unknown block label "hello" |
test/transform_error/top_level_statement created+6| ... | ... | @@ -0,0 +1,6 @@ |
| 1 | let func = @extern("func"); | |
| 2 | ||
| 3 | func(1); | |
| 4 | func(2); | |
| 5 | ||
| 6 | // error:3:1: top-level statements are not allowed |
test/transform_error/top_level_statement_err created+3| ... | ... | @@ -0,0 +1,3 @@ |
| 1 | func(1); | |
| 2 | ||
| 3 | // error:3:1: top-level statements are not allowed |