From a48b4aaf01270f1fd4f562c8265e4edbe910db19 Mon Sep 17 00:00:00 2001 From: chloe caruso Date: Sat, 27 Jul 2024 23:32:02 -0700 Subject: [PATCH] feat: add --run flag --- CHANGELOG.md | 1 + build.zig | 46 +++++++------- src/cli.zig | 77 ++++++++++++++++++++++-- src/deps/quickjs/build.zig | 106 ++++++++++++++++++++++++++++++++- src/deps/quickjs/build.zig.zon | 8 +++ 5 files changed, 206 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c13fab77916a4b75c6e920299304770e20fa9155..99b19c874c72a7dfc96a8671c3731bd88ff2b1a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.1.6 +- feat: add `--run` to execute the file in QuickJS instead of outputting source - fix: crash when printing unused struct declaration error - fix: provide array result type to sema - fix: crash when analyzing an empty array without a result type diff --git a/build.zig b/build.zig index 0cfbc52b92c39b96a987a587c65faf05ec060d55..46d942a1a0373a43e657e0ea48c4ae53f8dfe4cf 100644 --- a/build.zig +++ b/build.zig @@ -66,7 +66,7 @@ pub fn build(b: *Build) !void { const native_debug = buildWithTarget(b, b.graph.host, .Debug, .none, version, no_llvm); const main = buildWithTarget(b, target, optimize, tracing, version, no_llvm); - b.installArtifact(main.cli(b, .{})); + b.installArtifact(main.cli(b, BuildConfig.all)); inline for (.{ .{ main.cli(b, BuildConfig.minimal), "compiler" }, @@ -159,28 +159,25 @@ pub fn build(b: *Build) !void { @tagName(cross_target.result.os.tag), }, ) catch @panic("OOM"); - _ = name; // autofix const artifacts = buildWithTarget(b, cross_target, .ReleaseSafe, .none, version, no_llvm); - _ = artifacts; // autofix - // inline for (.{ - // .{ artifacts.compiler, "compiler" }, - // // .{ artifacts.lsp, "lsp" }, - // }) |entry| { - // const artifact, const bin_name = entry; - // package.dependOn(&b.addInstallFile( - // artifact.getEmittedBin(), - // b.pathJoin(&.{ - // "package", - // name, - // std.mem.concat(b.graph.arena, u8, &.{ - // bin_name, - // cross_target.result.exeFileExt(), - // }) catch @panic("OOM"), - // }), - // ).step); - // } + inline for (.{ + .{ artifacts.cli(b, BuildConfig.all), "compiler" }, + }) |entry| { + const artifact, const bin_name = entry; + package.dependOn(&b.addInstallFile( + artifact.getEmittedBin(), + b.pathJoin(&.{ + "package", + name, + std.mem.concat(b.graph.arena, u8, &.{ + bin_name, + cross_target.result.exeFileExt(), + }) catch @panic("OOM"), + }), + ).step); + } package.dependOn(&b.addInstallFile(b.path("CHANGELOG.md"), "package/CHANGELOG.md").step); package.dependOn(&b.addInstallFile(manual_out, "package/README.html").step); @@ -208,8 +205,6 @@ const Artifacts = struct { const use_llvm = if (a.no_llvm) false else null; const use_lld = if (a.no_llvm) false else null; - const tracer = b.dependency("tracer", dep_opts); - // Options const build_config = b.addOptions(); build_config.addOption(BuildConfig, "config", config); @@ -229,7 +224,12 @@ const Artifacts = struct { exe.root_module.addImport("build_options", a.opts_mod); exe.root_module.addImport("build_config", build_config_mod); exe.root_module.addImport("core", a.core); - exe.root_module.addImport("tracer", tracer.module("tracer")); + exe.root_module.addImport("tracer", b.dependency("tracer", dep_opts).module("tracer")); + + if (config.have_embedded_quickjs) { + exe.root_module.addImport("quickjs", b.dependency("quickjs", dep_opts).module("quickjs_libc")); + } + return exe; } }; diff --git a/src/cli.zig b/src/cli.zig index 8ebc9f783f2484dadbeb0901ac340ead4345270a..44f5cbf52e0f8ddc7dfd49e92ac76b405c383899 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -5,6 +5,7 @@ //! needing to compile the formatter. const have_debug_features = builtin.mode == .Debug; +const config = @import("build_config").config; pub fn main() !u8 { try tracer.init(); @@ -37,6 +38,7 @@ pub fn main() !u8 { defer if (exe_name) |path| alloc.free(path); var dump_ir = false; var dump_state: ?[]const u8 = null; + var run = false; var has_errors = false; { var args = try std.process.argsWithAllocator(alloc); @@ -61,6 +63,11 @@ pub fn main() !u8 { continue; } + if (config.have_embedded_quickjs and std.mem.eql(u8, arg, "--run")) { + run = true; + continue; + } + std.debug.print("error: unknown flag '{s}'\n", .{arg}); has_errors = true; break; @@ -89,7 +96,13 @@ pub fn main() !u8 { \\ , .{}); } - if (builtin.mode == .Debug) { + if (config.have_embedded_quickjs) { + std.debug.print( + \\ --run Run program using QuickJS + \\ + , .{}); + } + if (have_debug_features) { std.debug.print( \\ \\Debug Mode Flags: @@ -129,6 +142,7 @@ pub fn main() !u8 { error.TransformFail => { std.debug.print("Transform failed:\n", .{}); try fail_report.dumpToStdErr(&comp); + fail_report.deinit(comp.gpa); return 1; }, error.ParseFail => { @@ -148,17 +162,68 @@ pub fn main() !u8 { else => return err, }; - const output = std.io.getStdOut().writer(); - var bufw = std.io.bufferedWriter(output); - const writer = bufw.writer(); - try js_backend.link(&comp, entry_point, writer.any()); - try bufw.flush(); + if (config.have_embedded_quickjs and run) { + // Execute with QuickJS + var out: std.ArrayListUnmanaged(u8) = .{}; + defer out.deinit(alloc); + try js_backend.link(&comp, entry_point, out.writer(alloc).any()); + try out.append(alloc, 0); + try runWithQuickJs(out.items[0 .. out.items.len - 1 :0]); + } else { + // Output to standard out + const output = std.io.getStdOut().writer(); + var bufw = std.io.bufferedWriter(output); + const writer = bufw.writer(); + try js_backend.link(&comp, entry_point, writer.any()); + try bufw.flush(); + } } std.process.cleanExit(); return 0; } +fn runWithQuickJs(js: [:0]const u8) !void { + const qjs = @import("quickjs"); + + const runtime = qjs.JS_NewRuntime() orelse + return error.UnexpectedError; + defer qjs.JS_FreeRuntime(runtime); + const js_ctx = qjs.JS_NewContext(runtime) orelse + return error.UnexpectedError; + defer qjs.JS_FreeContext(js_ctx); + + // QuickJS provides a completely blank global context. It is a non-goal to + // be a full runtime, but adding simple globals to align with expectations + // is OK. + + // 0 and null is zero process args + qjs.js_std_add_helpers(js_ctx, 0, null); + + const result = qjs.JS_Eval( + js_ctx, + js.ptr, + js.len, + "test.js", + qjs.JS_EVAL_TYPE_GLOBAL, + ); + defer qjs.JS_FreeValue(js_ctx, result); + + if (qjs.JS_IsException(result) != 0) { + const exception = qjs.JS_GetException(js_ctx); + defer qjs.JS_FreeValue(js_ctx, exception); + + const exception_str = qjs.JS_ToCString(js_ctx, exception); + defer qjs.JS_FreeCString(js_ctx, exception_str); + + const slice = std.mem.span(exception_str); + + std.debug.print("Exception: {s}\n", .{slice}); + + std.process.exit(1); + } +} + const core = @import("core"); const Comp = core.Comp; const Report = core.Report; diff --git a/src/deps/quickjs/build.zig b/src/deps/quickjs/build.zig index 15a610abde29a5ecd1ed49ab1ad3c51d53482cca..175b6af946cc8c84cf9bfb1ba081db8fa05c92ab 100644 --- a/src/deps/quickjs/build.zig +++ b/src/deps/quickjs/build.zig @@ -2,6 +2,9 @@ pub fn build(b: *Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const pthread = WinPthreads.get(b, target, optimize); + const pthread_native = WinPthreads.get(b, b.graph.host, .Debug); + const src = b.dependency("quickjs", .{}); const version = std.mem.trim( @@ -35,8 +38,9 @@ pub fn build(b: *Build) !void { .name = "quickjs", .target = target, .optimize = optimize, + .link_libc = true, }); - + pthread.configure(libquickjs); libquickjs.addCSourceFiles(.{ .root = src.path(""), .files = &.{ @@ -52,11 +56,31 @@ pub fn build(b: *Build) !void { libquickjs.root_module.addCMacro(def.name, def.value); } + const libquickjs_libc = b.addStaticLibrary(.{ + .name = "quickjs_libc", + .target = target, + .optimize = optimize, + .link_libc = true, + }); + pthread.configure(libquickjs_libc); + libquickjs_libc.addCSourceFiles(.{ + .root = src.path(""), + .files = &.{ + "quickjs-libc.c", + }, + .flags = c_flags, + }); + for (defines) |def| { + libquickjs_libc.root_module.addCMacro(def.name, def.value); + } + const qjsc = b.addExecutable(.{ .name = "qjsc", .target = b.graph.host, .optimize = .Debug, + .link_libc = true, }); + pthread_native.configure(qjsc); qjsc.addCSourceFiles(.{ .root = src.path(""), .files = &.{ @@ -78,12 +102,13 @@ pub fn build(b: *Build) !void { .name = "qjs", .target = target, .optimize = optimize, + .link_libc = true, }); + pthread.configure(qjs); qjs.addCSourceFiles(.{ .root = src.path(""), .files = &.{ "qjs.c", - "quickjs-libc.c", }, .flags = c_flags, }); @@ -99,18 +124,32 @@ pub fn build(b: *Build) !void { qjs.root_module.addCMacro(def.name, def.value); } qjs.linkLibrary(libquickjs); + qjs.linkLibrary(libquickjs_libc); b.installArtifact(qjs); b.installArtifact(libquickjs); + b.installArtifact(libquickjs_libc); const translate = b.addTranslateC(.{ .root_source_file = src.path("quickjs.h"), .target = target, .optimize = optimize, - .link_libc = false, + .link_libc = true, }); + if (pthread.pthreads) |pt| translate.addIncludeDir(pt.include.getPath(b)); const module = translate.addModule("quickjs"); module.linkLibrary(libquickjs); + + const translate_libc = b.addTranslateC(.{ + .root_source_file = src.path("quickjs-libc.h"), + .target = target, + .optimize = optimize, + .link_libc = true, + }); + const module2 = translate_libc.addModule("quickjs_libc"); + if (pthread.pthreads) |pt| translate_libc.addIncludeDir(pt.include.getPath(b)); + module2.linkLibrary(libquickjs); + module2.linkLibrary(libquickjs_libc); } fn runQjsc(b: *Build, qjsc: *Build.Step.Compile, in: Build.LazyPath) Build.LazyPath { @@ -124,5 +163,66 @@ fn runQjsc(b: *Build, qjsc: *Build.Step.Compile, in: Build.LazyPath) Build.LazyP return out; } +/// Zig doesnt include winpthreads, so we build it from source. This helper +/// decides if it is needed, and provides a no-op `configure` function if it is +/// not actually needed. Call .configure on any object files you need. +const WinPthreads = struct { + pthreads: ?struct { + library: *Build.Step.Compile, + include: Build.LazyPath, + } = null, + + pub fn get( + b: *Build, + target: Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + ) WinPthreads { + if (target.result.os.tag == .windows) { + const dep = b.lazyDependency("mingw-w64", .{}) orelse + return .{}; + + const libpthread = b.addStaticLibrary(.{ + .name = "winpthreads", + .target = target, + .optimize = optimize, + .link_libc = true, + }); + + libpthread.addCSourceFiles(.{ + .root = dep.path("mingw-w64-libraries/winpthreads/src"), + .files = &.{ + "barrier.c", + "clock.c", + "cond.c", + "misc.c", + "mutex.c", + "nanosleep.c", + "ref.c", + "rwlock.c", + "sched.c", + "sem.c", + "spinlock.c", + "thread.c", + }, + }); + + libpthread.addIncludePath(dep.path("mingw-w64-libraries/winpthreads/include")); + + return .{ .pthreads = .{ + .library = libpthread, + .include = dep.path("mingw-w64-libraries/winpthreads/include"), + } }; + } + return .{}; + } + + pub fn configure(this: WinPthreads, compile: *Build.Step.Compile) void { + if (this.pthreads) |pthreads| { + compile.linkLibrary(pthreads.library); + compile.addIncludePath(pthreads.include); + } + } +}; + const std = @import("std"); const Build = std.Build; diff --git a/src/deps/quickjs/build.zig.zon b/src/deps/quickjs/build.zig.zon index 07543cf53f76810f26a6d3cc985de0218484a348..fc99011c54583532359f2576d2f220fa7242eceb 100644 --- a/src/deps/quickjs/build.zig.zon +++ b/src/deps/quickjs/build.zig.zon @@ -7,6 +7,14 @@ .url = "https://github.com/bellard/quickjs/archive/6e2e68fd0896957f92eb6c242a2e048c1ef3cae0.tar.gz", .hash = "1220d49e57fc131eebbf4b09fb100f5d802b909d2f36e15d2d849a72f354cd24489d", }, + + // Required for building "pthread.h" on Windows + // https://github.com/ziglang/zig/issues/10989 + .@"mingw-w64" = .{ + .url = "https://github.com/mirror/mingw-w64/archive/eff726c461e09f35eeaed125a3570fa5f807f02b.tar.gz", + .hash = "1220cb412e58a63679fd29943e90331c4cc4d301382d792b1fba570eabfb0b032c81", + .lazy = true, + }, }, .paths = .{ -- 2.54.0