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
153 changes: 153 additions & 0 deletions Compiler/Backend/LLVM/Tests/ArithmeticExecutionTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1

#include <Progmasoft/Catch3/Assertions.hpp>
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "Visual/XSharp/Backend/LLVM.hpp"
#include "Visual/XSharp/Core/IR.hpp"
#include "Visual/XSharp/Core/Wire.hpp"
#include "Visual/XSharp/Pipeline.hpp"

// Executable integer-division regressions. The existing backend tests check
// that the rounded-division helper appears in the generated IR; these run the
// code and compare each quotient and remainder with the value the language
// specification gives, for every sign combination, through both native
// optimizer settings.

namespace
{
namespace Core = Visual::XSharp::Core;
namespace Llvm = Visual::XSharp::Backend::LLVM;
namespace Pipeline = Visual::XSharp::Pipeline;

/// `int left = <left>; int right = <right>; return left <op> right;`
/// The operands are mutable locals so the operation is lowered as a
/// run-time instruction in the unoptimized pipeline.
[[nodiscard]] auto
Binary(Core::Primitive operation, std::int64_t left, std::int64_t right)
-> Core::Module
{
const auto local =
[](std::uint64_t id, std::u32string name, std::int64_t value) {
return Core::Statement::Bind(
{ { id, std::move(name) },
Core::Type::int64(),
true,
Core::Expression::Constant(value, Core::Type::int64()) });
};
const auto read = [](std::uint64_t id, std::u32string name) {
return Core::Expression::Variable({ id, std::move(name) },
Core::Type::int64());
};
return {
{ U"Arithmetic" },
{ Core::Function{
{ 1U, U"Evaluate" },
{},
Core::Type::int64(),
{ local(2U, U"left", left),
local(3U, U"right", right),
Core::Statement::Return(Core::Expression::InvokePrimitive(
operation,
{ read(2U, U"left"), read(3U, U"right") },
Core::Type::int64())) },
} }
};
}

[[nodiscard]] auto
Run(const Core::Module &module, bool optimize) -> std::int64_t
{
const auto encoded = Core::Wire::Encode(module);
REQUIRE(encoded);
Pipeline::Options options;
options.optimize_xpp = optimize;
options.optimize_xmm = optimize;
options.llvm.optimization = optimize ? Llvm::OptimizationLevel::Default
: Llvm::OptimizationLevel::Debug;
const auto pipeline = Pipeline::ConsumeCore(encoded.bytes, options);
REQUIRE(pipeline);
REQUIRE(pipeline.llvm);

constexpr std::string_view kSymbol = "Arithmetic.Evaluate.1";
Llvm::JitSession session;
REQUIRE_FALSE(session.AddModule(pipeline.llvm->bitcode,
"arithmetic-execution",
kSymbol,
Core::Type::int64()));
const auto result = session.InvokeScalar(kSymbol, Core::Type::int64());
REQUIRE(result);
return std::get<std::int64_t>(result.value->payload);
}

void
CheckBothPipelines(Core::Primitive operation,
std::int64_t left,
std::int64_t right,
std::int64_t expected)
{
CAPTURE(left, right);
const auto module = Binary(operation, left, right);
CHECK(Run(module, false) == expected);
CHECK(Run(module, true) == expected);
}

/// The specification's rounded division: nearest integer, exact halves
/// away from zero. Written without the compiler's own formula.
[[nodiscard]] auto
RoundedQuotient(std::int64_t left, std::int64_t right) -> std::int64_t
{
const bool negative = (left < 0) != (right < 0);
const std::int64_t dividend = left < 0 ? -left : left;
const std::int64_t divisor = right < 0 ? -right : right;
const std::int64_t magnitude = (2 * dividend + divisor) / (2 * divisor);
return negative ? -magnitude : magnitude;
}
} // namespace

TEST_CASE("rounded integer division matches the specification examples",
"[llvm][arithmetic][execution]")
{
// Spec/Language/Operators.vxs, "Rounded Integer Division //".
CheckBothPipelines(Core::Primitive::FloorDivide, 7, 2, 4);
CheckBothPipelines(Core::Primitive::FloorDivide, 6, 2, 3);
CheckBothPipelines(Core::Primitive::FloorDivide, 5, 2, 3);
CheckBothPipelines(Core::Primitive::FloorDivide, -7, 2, -4);
CheckBothPipelines(Core::Primitive::FloorDivide, -5, 2, -3);
// Exact halves round away from zero.
CheckBothPipelines(Core::Primitive::FloorDivide, 1, 2, 1);
CheckBothPipelines(Core::Primitive::FloorDivide, -1, 2, -1);
}

TEST_CASE("rounded integer division rounds to nearest for every sign pair",
"[llvm][arithmetic][execution]")
{
for (std::int64_t left = -13; left <= 13; ++left)
for (std::int64_t right = -5; right <= 5; ++right)
if (right != 0)
CheckBothPipelines(Core::Primitive::FloorDivide,
left,
right,
RoundedQuotient(left, right));
}

TEST_CASE("remainder keeps the sign of its left operand",
"[llvm][arithmetic][execution]")
{
// Spec/Language/Operators.vxs, "Remainder %".
CheckBothPipelines(Core::Primitive::Remainder, 7, 3, 1);
CheckBothPipelines(Core::Primitive::Remainder, -7, 3, -1);
CheckBothPipelines(Core::Primitive::Remainder, 7, -3, 1);
for (std::int64_t left = -9; left <= 9; ++left)
for (std::int64_t right = -4; right <= 4; ++right)
if (right != 0)
CheckBothPipelines(Core::Primitive::Remainder,
left,
right,
left % right);
}
1 change: 1 addition & 0 deletions Compiler/Backend/LLVM/Tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ cc_binary(
name = "llvm_backend_tests",
srcs = [
"CallableInvocationTests.cpp",
"ArithmeticExecutionTests.cpp",
"ConditionalExecutionTests.cpp",
"LLVMBackendTests.cpp",
"JitSessionTests.cpp",
Expand Down
125 changes: 125 additions & 0 deletions Compiler/Backend/LLVM/Tests/LoopExecutionTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,128 @@ TEST_CASE("nested loops transfer only within their own loop",
CheckBothPipelines(module, expected);
}
}

TEST_CASE("return inside a loop leaves the function with the current value",
"[llvm][loop][execution]")
{
// for (index = 0; index < limit; index++) {
// if (index == 4) { return total + 1000; }
// total = total + index;
// }
// return total;
for (std::int64_t limit = 0; limit < kLimits; ++limit)
{
const auto host = [limit]() -> std::int64_t {
std::int64_t total{};
for (std::int64_t index = 0; index < limit; ++index)
{
if (index == 4)
return total + 1000;
total += index;
}
return total;
};
CAPTURE(limit);
CheckBothPipelines(
LoopModule(Core::Statement::For(
Compare(Core::Primitive::LessThan, kIndex, limit),
{ Core::Statement::If(
Compare(Core::Primitive::Equal, kIndex, 4),
{ Core::Statement::Return(
Core::Expression::InvokePrimitive(
Core::Primitive::Add,
{ Variable(kTotal), Integer(1000) },
Core::Type::int64())) },
{}),
Add(kTotal, Variable(kIndex)) },
{ Increment(kIndex) })),
host());
}
}

TEST_CASE("return from a nested loop skips every enclosing update",
"[llvm][loop][execution]")
{
// The inner loop returns from inside a while that sits in a for body;
// neither the inner increment nor the outer update may run afterwards.
for (std::int64_t limit = 0; limit < 7; ++limit)
{
const auto host = [limit]() -> std::int64_t {
std::int64_t total{};
for (std::int64_t index = 0; index < limit; ++index)
{
std::int64_t inner{};
while (inner < 3)
{
if (index == 2 && inner == 1)
return total * 10 + index;
total += inner;
++inner;
}
}
return total;
};
auto reset
= Core::Statement::Assign({ kInner, Spelling(kInner) }, Integer(0));
auto module = LoopModule(Core::Statement::For(
Compare(Core::Primitive::LessThan, kIndex, limit),
{ reset,
Core::Statement::While(
Compare(Core::Primitive::LessThan, kInner, 3),
{ Core::Statement::If(
Compare(Core::Primitive::Equal, kIndex, 2),
{ When(kInner,
1,
Core::Statement::Return(
Core::Expression::InvokePrimitive(
Core::Primitive::Add,
{ Core::Expression::InvokePrimitive(
Core::Primitive::Multiply,
{ Variable(kTotal), Integer(10) },
Core::Type::int64()),
Variable(kIndex) },
Core::Type::int64()))) },
{}),
Add(kTotal, Variable(kInner)),
Increment(kInner) }) },
{ Increment(kIndex) }));
auto &body = module.functions.front().body;
body.insert(body.begin() + 2, Declare(kInner));
CAPTURE(limit);
CheckBothPipelines(module, host());
}
}

TEST_CASE("do-while returns from its body before the trailing test",
"[llvm][loop][execution]")
{
for (std::int64_t limit = 0; limit < kLimits; ++limit)
{
const auto host = [limit]() -> std::int64_t {
std::int64_t total{};
std::int64_t index{};
do
{
++index;
if (index == 6)
return -total;
total += index;
} while (index < limit);
return total;
};
CAPTURE(limit);
CheckBothPipelines(
LoopModule(Core::Statement::DoWhile(
{ Increment(kIndex),
When(
kIndex,
6,
Core::Statement::Return(Core::Expression::InvokePrimitive(
Core::Primitive::Negate,
{ Variable(kTotal) },
Core::Type::int64()))),
Add(kTotal, Variable(kIndex)) },
Compare(Core::Primitive::LessThan, kIndex, limit))),
host());
}
}
4 changes: 3 additions & 1 deletion Compiler/Haskell/Core/src/Visual/XSharp/Core.hs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ data CorePrimitive
CoreMultiply
| -- | Truncating division.
CoreDivide
| -- | Floor division.
| {- | Rounded division @//@: nearest integer, halves away from zero. The
constructor keeps its historical name; it never floors.
-}
CoreFloorDivide
| -- | Remainder operation.
CoreRemainder
Expand Down
16 changes: 9 additions & 7 deletions Compiler/Headers/Visual/XSharp/Core/CorePrep.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -528,13 +528,15 @@ namespace visual_xsharp::core
/// Operation encoded by a CorePrep instruction.
enum class Operation : std::uint8_t
{
Copy, ///< Copy one operand into the destination.
Call, ///< Call the function supplied as the first operand.
Add, ///< Arithmetic addition.
Subtract, ///< Arithmetic subtraction.
Multiply, ///< Arithmetic multiplication.
Divide, ///< Truncating arithmetic division.
FloorDivide, ///< Floor-rounded arithmetic division.
Copy, ///< Copy one operand into the destination.
Call, ///< Call the function supplied as the first operand.
Add, ///< Arithmetic addition.
Subtract, ///< Arithmetic subtraction.
Multiply, ///< Arithmetic multiplication.
Divide, ///< Truncating arithmetic division.
/// Rounded division `//`: nearest integer, halves away from zero.
/// The enumerator keeps its historical name; it never floors.
FloorDivide,
Remainder, ///< Arithmetic remainder.
LessThan, ///< Ordered less-than comparison.
LessEqual, ///< Ordered less-than-or-equal comparison.
Expand Down
12 changes: 7 additions & 5 deletions Compiler/Headers/Visual/XSharp/Core/IR.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ namespace Visual::XSharp::Core
/// Built-in operation represented directly in a typed Core expression.
enum class Primitive : std::uint8_t
{
Add, ///< Arithmetic addition.
Subtract, ///< Arithmetic subtraction.
Multiply, ///< Arithmetic multiplication.
Divide, ///< Truncating arithmetic division.
FloorDivide, ///< Floor-rounded arithmetic division.
Add, ///< Arithmetic addition.
Subtract, ///< Arithmetic subtraction.
Multiply, ///< Arithmetic multiplication.
Divide, ///< Truncating arithmetic division.
/// Rounded division `//`: nearest integer, halves away from zero.
/// The enumerator keeps its historical name; it never floors.
FloorDivide,
Remainder, ///< Arithmetic remainder.
LessThan, ///< Ordered less-than comparison.
LessEqual, ///< Ordered less-than-or-equal comparison.
Expand Down
26 changes: 14 additions & 12 deletions Compiler/Headers/Visual/XSharp/Xmm/IR.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,20 @@ namespace visual_xsharp::xmm
/// Typed operation encoded by an Xmm instruction.
enum class Opcode : std::uint8_t
{
LoadImmediate, ///< Materialize a constant into a virtual register.
Move, ///< Copy a value into the destination register.
Call, ///< Invoke a function value or direct function.
Add, ///< Arithmetic addition.
Subtract, ///< Arithmetic subtraction.
Multiply, ///< Arithmetic multiplication.
Divide, ///< Truncating arithmetic division.
FloorDivide, ///< Floor-rounded arithmetic division.
Remainder, ///< Arithmetic remainder.
CompareLess, ///< Ordered less-than comparison.
CompareLessEqual, ///< Ordered less-than-or-equal comparison.
CompareGreater, ///< Ordered greater-than comparison.
LoadImmediate, ///< Materialize a constant into a virtual register.
Move, ///< Copy a value into the destination register.
Call, ///< Invoke a function value or direct function.
Add, ///< Arithmetic addition.
Subtract, ///< Arithmetic subtraction.
Multiply, ///< Arithmetic multiplication.
Divide, ///< Truncating arithmetic division.
/// Rounded division `//`: nearest integer, halves away from zero.
/// The enumerator keeps its historical name; it never floors.
FloorDivide,
Remainder, ///< Arithmetic remainder.
CompareLess, ///< Ordered less-than comparison.
CompareLessEqual, ///< Ordered less-than-or-equal comparison.
CompareGreater, ///< Ordered greater-than comparison.
CompareGreaterEqual, ///< Ordered greater-than-or-equal comparison.
CompareEqual, ///< Value equality comparison.
CompareNotEqual, ///< Value inequality comparison.
Expand Down
16 changes: 9 additions & 7 deletions Compiler/Headers/Visual/XSharp/Xpp/IR.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ namespace visual_xsharp::xpp
/// Typed operation encoded by an Xpp instruction.
enum class Opcode : std::uint8_t
{
Copy, ///< Copy an operand to the destination.
Call, ///< Invoke the function in the first operand.
Add, ///< Arithmetic addition.
Subtract, ///< Arithmetic subtraction.
Multiply, ///< Arithmetic multiplication.
Divide, ///< Truncating division.
FloorDivide, ///< Floor-rounded division.
Copy, ///< Copy an operand to the destination.
Call, ///< Invoke the function in the first operand.
Add, ///< Arithmetic addition.
Subtract, ///< Arithmetic subtraction.
Multiply, ///< Arithmetic multiplication.
Divide, ///< Truncating division.
/// Rounded division `//`: nearest integer, halves away from zero.
/// The enumerator keeps its historical name; it never floors.
FloorDivide,
Remainder, ///< Arithmetic remainder.
CompareLess, ///< Ordered less-than comparison.
CompareLessEqual, ///< Ordered less-than-or-equal comparison.
Expand Down
Loading
Loading