Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "wavec"
version = "0.2.1-pre-beta"
version = "0.0.1-alpha-dev"
edition = "2021"

[lib]
Expand All @@ -14,6 +14,7 @@ parser = { path = "front/parser" }
hir = { path = "front/hir" }
error = { path = "front/error" }
llvm = { path = "./llvm", default-features = false }
whale = { path = "./whale" }

[features]
default = ["llvm-target-core64", "llvm-target-loongarch", "llvm-target-wasm"]
Expand All @@ -31,6 +32,7 @@ members = [
"front/parser",
"front/hir",
"llvm",
"whale",
"front/error",
"utils",
".",
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,36 @@ wavec print target-spec --target riscv64-unknown-linux-gnu --format=json

Run `wavec --help` for the complete CLI contract.

## Experimental Whale backend

Development is now `0.0.1-alpha-dev`. LLVM remains the default backend. The
existing `--whale` flag selects the initial Linux amd64, O0 integration:

```shell
wavec --whale check sample.wave
wavec --whale build sample.wave --emit=ir --out-dir output
```

The second command writes verified Whale IR to `output/sample.wir`. This path
supports scalar functions and direct calls, initialized local variables,
assignment, `if`/`else`, `while`, and scalar arithmetic and comparisons. Wave's
typed HIR supplies calculation types and ordered conversions. Short-circuit
logical operators preserve conditional evaluation.

This integration does not yet generate executables, objects, or assembly.
Aggregates, pointers, async, foreign calls, I/O, integers wider than 128 bits,
checked division/shifts, and float-to-integer conversions are rejected explicitly.
Unsupported requests never fall back to LLVM. `--whale check` also verifies
backend support, whereas the default `check` performs frontend validation.

The root `whale/` workspace crate owns the Whale backend alongside `llvm/`.
The compiler driver in `src/` selects and invokes these backends. The `whale/`
crate uses upstream Whale IR as a Rust Git dependency from the official
repository's `master` branch.
`Cargo.lock` records the tested commit; `cargo build --locked` reuses it. Advancing
Whale uses `cargo update -p ir`, followed by integration tests and review of the
lockfile change. Wave does not require a separately installed Whale executable.

## Target families

| Architecture | Hosted targets | Freestanding target |
Expand Down
12 changes: 8 additions & 4 deletions VERSION.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,15 @@ example, after the final `pre-beta` release, the first `alpha` release starts at

## Current Sequence

`v0.2.0-pre-beta` is the latest public `pre-beta` release.
`v0.2.1-pre-beta` is the final public `pre-beta` release.

The repository uses `v0.2.1-pre-beta-dev` while the next release is under
development. The `-dev` suffix is removed when the release candidate is ready
to publish as `v0.2.1-pre-beta`.
The repository uses `v0.0.1-alpha-dev` while LLVM and Whale integration is under
development. LLVM remains the default backend; Whale is selected explicitly
with `--whale`. The `-dev` suffix is removed when the release is ready to publish
as `v0.0.1-alpha`.

The stage transition resets the release counter. Installers must determine the
latest published release by publication order, not by comparing version numbers.

## Rules

Expand Down
150 changes: 142 additions & 8 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,12 +274,6 @@ where
}

fn dispatch(mut global: Global, cmd: CliCommand) -> Result<(), CliError> {
if global.whale.enabled {
return Err(CliError::usage(
"TODO: --whale backend is reserved but not implemented yet",
));
}

if global.std_root.is_some() && matches!(&cmd, CliCommand::Build(_) | CliCommand::Print { .. })
{
let root = parser::import::ResolvedStdRoot::resolve(global.std_root.as_deref())
Expand All @@ -289,7 +283,7 @@ fn dispatch(mut global: Global, cmd: CliCommand) -> Result<(), CliError> {

match cmd {
CliCommand::Version => {
print_version();
print_version_for_backend(global.whale.enabled);
Ok(())
}
CliCommand::Help => {
Expand All @@ -311,13 +305,140 @@ fn dispatch(mut global: Global, cmd: CliCommand) -> Result<(), CliError> {
}
}

/// Whale currently produces verified IR only. Keep its capability checks out of
/// LLVM target resolution: an LLVM installation must not decide Whale support.
fn dispatch_whale_build(global: &Global, build: &BuildRequest) -> Result<(), CliError> {
let mut global = global.clone();
let target = global
.llvm
.target
.get_or_insert_with(|| "x86_64-unknown-linux-gnu".into());
if target != "x86_64-unknown-linux-gnu" {
return Err(CliError::usage(
"Whale currently supports only --target x86_64-unknown-linux-gnu",
));
}
if global.opt != "-O0" {
return Err(CliError::usage("Whale currently supports only -O0"));
}
if !build.emit.is_check()
&& !matches!(build.emit.as_set(), Some(set) if set.len() == 1 && set.contains(&EmitKind::Ir))
{
return Err(CliError::usage("Whale currently supports check and --emit=ir (.wir) only; executable, object, assembly and bitcode output are not available"));
}
let llvm = &global.llvm;
if build.run
|| build.link_only
|| build.shared
|| build.static_link
|| build.pie.is_some()
|| build.freestanding
|| build.entry.is_some()
|| build.linker_script.is_some()
|| build.no_start_files
|| !global.link.libs.is_empty()
|| !global.link.paths.is_empty()
|| llvm.cpu.is_some()
|| llvm.features.is_some()
|| llvm.abi.is_some()
|| llvm.isa.is_some()
|| llvm.code_model.is_some()
|| llvm.relocation_model.is_some()
|| llvm.sysroot.is_some()
|| llvm.linker.is_some()
|| !llvm.link_args.is_empty()
|| llvm.no_default_libs
|| global.debug.mc
|| global.debug.hex
{
return Err(CliError::usage("Whale IR does not support execution, linking, LLVM tuning, or machine-code debug options"));
}
let classified = classify_inputs(build)?;
if classified.is_empty() || classified.iter().any(|i| i.kind != InputKind::Wave) {
return Err(CliError::usage("Whale requires Wave source inputs"));
}
if build.emit.is_check() && (build.output.is_some() || build.out_dir.is_some()) {
return Err(CliError::usage(
"--emit=check does not produce artifacts; remove -o/--out-dir",
));
}
if build.output.is_some() && (classified.len() != 1 || build.out_dir.is_some()) {
return Err(CliError::usage(
"Whale -o requires one input and cannot be combined with --out-dir",
));
}
if global.dep.resolved_std_root.is_none() {
global.dep.resolved_std_root = Some(parser::import::ResolvedStdRoot::resolve(None));
}
let outputs: Vec<_> = if build.emit.is_check() {
vec![]
} else {
classified
.iter()
.enumerate()
.map(|(i, input)| {
let output = build.output.clone().unwrap_or_else(|| {
resolve_extra_emit_output_path(build, input, EmitKind::Ir, i, classified.len())
.with_extension("wir")
});
(output, None)
})
.collect()
};
let mut inputs = build.inputs.clone();
for input in &classified {
inputs.extend(runner::wave_input_paths(
&input.path,
&global.dep,
&global.llvm,
));
}
crate::output_guard::validate(&inputs, &outputs)?;
if build.dry_run {
for (i, input) in classified.iter().enumerate() {
println!(
"Whale / Linux amd64 / O0: {} -> {}",
input.path.display(),
outputs
.get(i)
.map(|(p, _)| p.display().to_string())
.unwrap_or_else(|| "verify only".into())
);
}
return Ok(());
}
// Lower every source before publishing any output, including multi-input requests.
let texts: Vec<_> = classified
.iter()
.map(|input| {
runner::emit_whale_ir_text(&input.path, &global.debug, &global.dep, &global.llvm)
})
.collect();
if global.debug.ir {
for text in &texts {
println!("{text}");
}
}
for ((output, _), text) in outputs.iter().zip(texts) {
ensure_parent_dir(output)?;
let pending = PendingOutput::new(output)?;
fs::write(pending.path(), text)?;
pending.commit()?;
}
Ok(())
}

fn dispatch_build(global: &Global, build: &BuildRequest) -> Result<(), CliError> {
let mut build = build.clone();
if global.error_format == ErrorFormat::Json {
build.error_format = ErrorFormat::Json;
}
configure_wave_error_format(build.error_format);

if global.whale.enabled {
return dispatch_whale_build(global, &build);
}

let mut effective_global = effective_global_for_build(global, &build);
resolve_target_configuration(&mut effective_global.llvm)?;
resolve_build_sysroot(&mut effective_global.llvm, &build);
Expand Down Expand Up @@ -441,6 +562,9 @@ fn dispatch_print(
target_arg: Option<&str>,
format: PrintFormat,
) -> Result<(), CliError> {
if global.whale.enabled {
return Err(CliError::usage("Whale capability queries are not yet available; supported target: x86_64-unknown-linux-gnu, optimization: O0, output: IR"));
}
let target = target_arg
.map(|s| s.to_string())
.or_else(|| global.llvm.target.clone())
Expand Down Expand Up @@ -4432,6 +4556,10 @@ pub fn print_usage() {
}

pub fn print_version() {
print_version_for_backend(false);
}

fn print_version_for_backend(whale: bool) {
let os = format!("({})", get_os_pretty_name()).color("117,117,117");

println!(
Expand All @@ -4441,7 +4569,9 @@ pub fn print_version() {
os
);

if let Some(backend) = llvm::backend() {
if whale {
println!(" backend: Whale IR (experimental, Linux amd64, O0)");
} else if let Some(backend) = llvm::backend() {
println!(" backend: {}", backend.color("117,117,117"));
} else {
println!("{}", " backend: unknown backend".color("117,117,117"));
Expand Down Expand Up @@ -4490,6 +4620,10 @@ pub fn print_help() {
);
println!(" {:<22} {}", "--help".color("38,139,235"), "Show help");

println!("\nBackend selection:");
println!(
" --whale Experimental Whale IR (Linux amd64, O0); LLVM is the default"
);
println!("\nBuild options:");
println!(
" {:<24} {}",
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
//!
//! This crate owns command-line planning, source preparation, diagnostics, and
//! link orchestration. Language parsing lives in the frontend crates, while
//! target-specific lowering lives in the `llvm` crate.
//! target-specific lowering lives in the `llvm` and `whale` crates.

// CLI tables and compiler phase boundaries intentionally favor explicit data.
#![allow(
Expand Down
32 changes: 32 additions & 0 deletions src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,38 @@ fn frontend_prepare_wave_hir(
(code, hir)
}

pub(crate) fn emit_whale_ir_text(
file_path: &Path,
debug: &DebugFlags,
dep: &DepFlags,
target: &LlvmFlags,
) -> String {
let (source, program) = frontend_prepare_wave_hir(file_path, debug, dep, Some(target));
match whale::emit_ir(&program) {
Ok(text) => text,
Err(diagnostic) => {
let diagnostic_source = diagnostic
.span
.as_ref()
.and_then(|s| fs::read_to_string(&s.file).ok())
.unwrap_or(source);
WaveError::new(
WaveErrorKind::InvalidStatement(diagnostic.message.clone()),
diagnostic.message,
file_path.display().to_string(),
0,
0,
)
.with_code("E4001")
.with_source_code(diagnostic_source)
.with_span(diagnostic.span.as_ref())
.with_context("Whale IR lowering")
.display_auto();
process::exit(1);
}
}
}

pub(crate) unsafe fn check_wave_file(
file_path: &Path,
debug: &DebugFlags,
Expand Down
Loading
Loading