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
15 changes: 9 additions & 6 deletions front/error/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,19 +399,21 @@ impl WaveError {
let width = (end + 1).to_string().len().max(2);

for (i, source_line) in lines.iter().enumerate().take(end + 1).skip(start) {
let rendered =
utils::display_width::diagnostic_line(source_line, col, self.span_len);
let ln = i + 1;
let ln_str = format!("{:>width$}", ln, width = width);
eprintln!(
" {} {} {}",
ln_str.color("38,139,235").bold(),
pipe,
source_line
rendered.text
);
if ln == line {
let pad = " ".repeat(width);
let spaces = " ".repeat(col.saturating_sub(1));
let spaces = " ".repeat(rendered.caret_offset);
let marks = "^"
.repeat(self.span_len.max(1))
.repeat(rendered.caret_width)
.color(self.severity_color())
.bold();
match &self.label {
Expand All @@ -427,19 +429,20 @@ impl WaveError {
}

if let Some(source_line) = &self.source {
let rendered = utils::display_width::diagnostic_line(source_line, col, self.span_len);
let width = line.to_string().len().max(2);
let ln_str = format!("{:>width$}", line, width = width);
eprintln!(
" {} {} {}",
ln_str.color("38,139,235").bold(),
pipe,
source_line
rendered.text
);

let pad = " ".repeat(width);
let spaces = " ".repeat(col.saturating_sub(1));
let spaces = " ".repeat(rendered.caret_offset);
let marks = "^"
.repeat(self.span_len.max(1))
.repeat(rendered.caret_width)
.color(self.severity_color())
.bold();
match &self.label {
Expand Down
2 changes: 1 addition & 1 deletion front/parser/src/async_intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub fn signature(
}
"__wave_async_sleep" => {
no_types()?;
(vec![Int(64)], future(Void))
(vec![Int(64)], future(Int(32)))
}
"__wave_async_cancel_join" => {
no_types()?;
Expand Down
2 changes: 1 addition & 1 deletion front/parser/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use std::path::{Path, PathBuf};

/// Compiler/standard-library syntax contract understood by this parser.
/// Bump this together with std/manifest.json when compatibility is broken.
pub const STD_COMPATIBILITY_REVISION: u64 = 4;
pub const STD_COMPATIBILITY_REVISION: u64 = 5;

#[derive(Debug, Clone, Default)]
pub struct TargetConditionContext {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
// This file is part of the Wave language project.
// SPDX-License-Identifier: MPL-2.0
//! Freestanding arithmetic legalization. Helpers contain no host calls and use
//! only operations that WebAssembly can lower without a runtime SDK.
//! only operations the selected target can lower without a runtime SDK.
mod templates;
use super::target::CodegenTarget;
use inkwell::{
context::Context,
memory_buffer::MemoryBuffer,
module::{Linkage, Module},
values::{BasicValue, InstructionOpcode, InstructionValue},
values::{AnyValue, BasicValue, InstructionOpcode, InstructionValue},
};
use std::collections::HashMap;

/// Run after optimization so unused operations never pull in runtime support.
/// Functions are private and cached by operation, independently of user symbols.
pub(super) fn lower<'ctx>(context: &'ctx Context, module: &Module<'ctx>) -> Result<(), String> {
pub(super) fn lower<'ctx>(
context: &'ctx Context,
module: &Module<'ctx>,
target: CodegenTarget,
) -> Result<(), String> {
let builder = context.create_builder();
let mut helpers = HashMap::new();
loop {
let instructions: Vec<_> = module
.get_functions()
.flat_map(|f| f.get_basic_blocks())
.flat_map(|b| b.get_instructions())
.filter(|i| operation(*i).is_some())
.filter(|i| operation(*i, target).is_some())
.collect();
if instructions.is_empty() {
break;
}
for instruction in instructions {
let Some(key) = operation(instruction) else {
let Some(key) = operation(instruction, target) else {
continue;
};
let function = if let Some(function) = helpers.get(&key) {
Expand Down Expand Up @@ -76,14 +81,51 @@ pub(super) fn lower<'ctx>(context: &'ctx Context, module: &Module<'ctx>) -> Resu
module.verify().map_err(|e| e.to_string())
}

fn operation(instruction: InstructionValue<'_>) -> Option<String> {
fn operation(instruction: InstructionValue<'_>, target: CodegenTarget) -> Option<String> {
use InstructionOpcode::*;
if !matches!(
instruction.get_opcode(),
UDiv | SDiv | URem | SRem | Mul | Shl | LShr | AShr | UIToFP | SIToFP | FPToUI | FPToSI
) {
return None;
}
// LLVM's 64-bit native targets expand i128 multiply and shifts into native
// instructions. WebAssembly requires explicit limb legalization for these.
let wasm = matches!(
target,
CodegenTarget::Wasm32Unknown | CodegenTarget::Wasm32WasiP1 | CodegenTarget::Wasm64Unknown
);
if !wasm && matches!(instruction.get_opcode(), Mul | Shl | LShr | AShr) {
return None;
}
if !wasm && matches!(instruction.get_opcode(), UDiv | SDiv | URem | SRem) {
// LLVM's native DAG lowering expands power-of-two divisors into
// shifts/masks (with signed rounding adjustments), even at O0.
if let Some(divisor) = instruction
.get_operand(1)
.and_then(|v| v.value())
.filter(|v| v.is_int_value())
{
let divisor = divisor.into_int_value();
if divisor.is_const() {
let text = divisor.print_to_string().to_string();
if let Some(value) = text
.split_whitespace()
.last()
.and_then(|s| s.parse::<i128>().ok())
{
let magnitude = if matches!(instruction.get_opcode(), SDiv | SRem) {
value.unsigned_abs()
} else {
value as u128
};
if magnitude.is_power_of_two() {
return None;
}
}
}
}
}
let source = instruction
.get_operand(0)?
.value()?
Expand Down Expand Up @@ -120,3 +162,21 @@ fn operation(instruction: InstructionValue<'_>) -> Option<String> {
};
Some(format!("{opcode}.{source}.{target}"))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn preserves_vector_division_created_by_the_optimizer() {
let context = Context::create();
let module = context.create_module_from_ir(MemoryBuffer::create_from_memory_range_copy(
b"define <16 x i16> @vector(<16 x i16> %a) { %result = udiv <16 x i16> %a, splat (i16 251)\n ret <16 x i16> %result }",
"vector-runtime-regression",
)).unwrap();
lower(&context, &module, CodegenTarget::FreeBsdX86_64).unwrap();
let ir = module.print_to_string().to_string();
assert!(ir.contains("udiv <16 x i16>"));
assert!(!ir.contains("__wave.runtime."));
}
}
7 changes: 2 additions & 5 deletions llvm/src/codegen/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1486,11 +1486,8 @@ fn build_module(
codegen_trace("skip optimization passes");
}

if abi_target == CodegenTarget::Wasm64Unknown {
super::wasm_runtime::lower(context, module).map_err(|e| {
CodegenError::new(CodegenPhase::Lowering, "lower wasm arithmetic runtime", e)
})?;
}
super::arithmetic_runtime::lower(context, module, abi_target)
.map_err(|e| CodegenError::new(CodegenPhase::Lowering, "lower arithmetic runtime", e))?;

codegen_trace("finish module");
Ok(GeneratedModule {
Expand Down
2 changes: 1 addition & 1 deletion llvm/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ mod validation;

pub(crate) mod conversions;

mod wasm_runtime;
mod arithmetic_runtime;
2 changes: 1 addition & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2460,7 +2460,7 @@ fn link_objects(
let stdout = String::from_utf8_lossy(&out.stdout).trim().to_string();

let hint = if llvm::backend::is_windows_msvc_target(&target) {
"\nMSVC output requires matching Windows SDK (UM/UCRT) and VC runtime libraries; run a Developer Command Prompt for the target architecture or supply their directories with -L. Arithmetic builtins such as __udivti3 require the target-matched LLVM 21 compiler-rt library; set WAVE_LLVM_HOME to that SDK."
"\nMSVC output requires matching Windows SDK (UM/UCRT) and VC runtime libraries; run a Developer Command Prompt for the target architecture or supply their directories with -L."
} else {
""
};
Expand Down
Loading
Loading