diff --git a/nautilus/include/nautilus/val_std.hpp b/nautilus/include/nautilus/val_std.hpp index 767037a0d..5bb4596e3 100644 --- a/nautilus/include/nautilus/val_std.hpp +++ b/nautilus/include/nautilus/val_std.hpp @@ -28,7 +28,8 @@ * | Parameterised construction | Yes | `val(val, val, …)` — args unwrapped via `unwrap_val_t` | * | Copy construction | Yes | `memcpy` for trivially-copyable; otherwise `invoke`| * | Copy assignment | Yes | same as copy construction | - * | Move construction/assignment | No | Not provided; use copy | + * | Move construction | Yes | Transfers the underlying alloca; no new allocation | + * | Move assignment | Yes | Destroys the LHS storage, then transfers from RHS | * | Destruction | Yes | Non-trivial dtor forwarded through `invoke()` | * | Field read (`get`) | Yes | Returns `val` for arithmetic/pointer fields | * | Field write (`set`) | Yes | Accepts `val` or plain `F` | @@ -111,8 +112,10 @@ #include "nautilus/val_base.hpp" #include "nautilus/val_concepts.hpp" #include "nautilus/val_memcpy.hpp" +#include #include #include +#include namespace nautilus { @@ -174,6 +177,11 @@ class val : public val_base { // alloca itself appear as first-class operations in the IR. val value_ptr; + // True after a move transferred ownership of value_ptr to another object. + // Suppresses the destructor's traced destruct() call and the heap free so + // that the resource is released exactly once. + bool moved_ = false; + static void construct(ValueType* ptr) { new (ptr) ValueType(); } @@ -198,6 +206,25 @@ class val : public val_base { new (ptr) ValueType(args...); } + // Releases the resources owned by value_ptr (traced destruct + heap free in + // interpreter mode) unless this object has been moved-from. Used by both + // the destructor and move assignment. + void release_storage() { + if (moved_) { + return; + } + if constexpr (!std::is_trivially_destructible_v) { + invoke(destruct, value_ptr); + } +#ifdef ENABLE_TRACING + if (!tracing::inTracer()) { + ::operator delete(value_ptr.value); + } +#else + ::operator delete(value_ptr.value); +#endif + } + public: // Default-constructs the object on the traced stack. // For trivially-default-constructible types the ctor call is elided. @@ -217,13 +244,30 @@ class val : public val_base { } } + // Move-constructs from another val. + // Transfers the underlying alloca/heap pointer; no new allocation, no copy. + // In tracing mode this constructs the new value_ptr directly from the source's + // SSA ref instead of going through val's copy ctor, which would emit an + // extra traceCopy/ASSIGN op into the IR for what is logically just an alias. + // The source is left in a moved-from state and its destructor becomes a no-op. +#ifdef ENABLE_TRACING + val(val&& other) noexcept : value_ptr(other.value_ptr.value, other.value_ptr.getState()) { + other.moved_ = true; + } +#else + val(val&& other) noexcept : value_ptr(other.value_ptr.value) { + other.moved_ = true; + } +#endif + // Constructs the object from one or more traced (val) or plain arguments. // Each argument's raw type is deduced via unwrap_val_t, which produces the concrete // construct_with instantiation passed to invoke(). - // The non-template copy constructor above always wins for same-type copies, so - // this template never conflicts with it. + // The same-type guard ensures the dedicated copy/move constructors above are + // always selected for val arguments instead of this template. template - requires(sizeof...(ValArgs) > 0) + requires(sizeof...(ValArgs) > 0 && + !(sizeof...(ValArgs) == 1 && (std::same_as, val> || ...))) val(ValArgs&&... args) : value_ptr(details::nautilus_alloca()) { invoke(construct_with...>, value_ptr, std::forward(args)...); } @@ -239,6 +283,19 @@ class val : public val_base { return *this; } + // Move-assigns from another val. + // Releases this object's storage, then transfers ownership of other's storage. + val& operator=(val&& other) noexcept { + if (std::addressof(other) == this) { + return *this; + } + release_storage(); + value_ptr = other.value_ptr; + moved_ = false; + other.moved_ = true; + return *this; + } + /** * Read a direct data member. * @@ -303,18 +360,10 @@ class val : public val_base { } // Destroys the object. For trivially-destructible types the dtor call is elided. + // A moved-from val skips both the traced destruct and the heap free so the + // resource is released exactly once. ~val() { - if constexpr (!std::is_trivially_destructible_v) { - invoke(destruct, value_ptr); - } - // in interpreter mode the value is allocated on heap, so we remove the allocation here -#ifdef ENABLE_TRACING - if (!tracing::inTracer()) { - ::operator delete(value_ptr.value); - } -#else - ::operator delete(value_ptr.value); -#endif + release_storage(); } }; } // namespace nautilus diff --git a/nautilus/test/common/ValueTypeFunctions.hpp b/nautilus/test/common/ValueTypeFunctions.hpp index a13145faa..1edd2bdd0 100644 --- a/nautilus/test/common/ValueTypeFunctions.hpp +++ b/nautilus/test/common/ValueTypeFunctions.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,129 @@ struct TestWithDtor { } }; +// Struct that counts constructions and destructions globally so tests can +// assert that move semantics do not leak or double-destruct objects. +struct CountedDtor { + static inline int ctor_count = 0; + static inline int dtor_count = 0; + int32_t v; + CountedDtor() : v(0) { + ++ctor_count; + } + CountedDtor(const CountedDtor& other) : v(other.v) { + ++ctor_count; + } + CountedDtor& operator=(const CountedDtor& other) { + v = other.v; + return *this; + } + ~CountedDtor() { + ++dtor_count; + } +}; + +// Move-construct from a trivially-copyable struct. The source's data must +// be observable through the moved-to object. +val moveConstructTrivial() { + val original; + original.set(&Test::a, 42); + original.set(&Test::b, 10); + val moved(std::move(original)); + return moved.get(&Test::a) + moved.get(&Test::b); +} + +// Move-construct from a non-trivially-destructible struct. +val moveConstructNonTrivial() { + val original; + original.set(&TestWithDtor::value, 99); + val moved(std::move(original)); + return moved.get(&TestWithDtor::value); +} + +// Move-assign overwrites the LHS storage with the RHS storage. +val moveAssign() { + val a; + a.set(&Test::a, 1); + val b; + b.set(&Test::a, 99); + b = std::move(a); + return b.get(&Test::a); +} + +// Self-move-assignment must not destroy or corrupt the value. The ref alias +// hides the self-move from -Wself-move; the operator= must still detect it +// at runtime and short-circuit. +val moveAssignSelf() { + val a; + a.set(&Test::a, 5); + val& ref = a; + ref = std::move(a); + return a.get(&Test::a); +} + +// Move + destruct of a CountedDtor object. +// At runtime ctor_count and dtor_count must end balanced and >= 1. +val moveDtorBalance() { + val a; + a.set(&CountedDtor::v, 7); + val b(std::move(a)); + return b.get(&CountedDtor::v); +} + +// Returning a named local: NRVO is allowed, the move ctor is the fallback. +val makeTest(val x) { + val t; + t.set(&Test::a, x); + t.set(&Test::b, 0); + return t; +} + +val returnByValue(val x) { + val r = makeTest(x); + return r.get(&Test::a); +} + +// Conditional return of two named locals: NRVO cannot apply, so the implicit +// move on return is what avoids a deep copy. +val makeTestCond(val x) { + val a; + a.set(&Test::a, 1); + val b; + b.set(&Test::a, 2); + if (x > 0) { + return a; + } + return b; +} + +val returnByValueCond(val x) { + val r = makeTestCond(x); + return r.get(&Test::a); +} + +// Static (fully-unrolled) loop body that constructs a fresh val each +// iteration and copy-assigns it into a val declared outside the loop. +// With N=3 unrolled iterations the IR has 4 allocas: 1 for `outer` plus +// 1 per unrolled iteration for `inner`. The copy-assignment writes through +// `outer.value_ptr` rather than reallocating, so `outer` keeps its single slot. +val staticLoopAssignStructToOuter() { + val outer; + outer.set(&Test::a, 0); + outer.set(&Test::b, 0); + for (static_val i = 0; i < 3; i = i + 1) { + val inner; + inner.set(&Test::a, i + 1); + inner.set(&Test::b, (i + 1) * 10); + outer = inner; + } + return outer.get(&Test::a) + outer.get(&Test::b); +} + +static_assert(std::is_move_constructible_v>); +static_assert(std::is_move_assignable_v>); +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(std::is_nothrow_move_assignable_v>); + // Structs with mixed-alignment members to exercise field_offset padding. // Layout: [i8][3 pad][i32][i64] = 16 bytes, alignof = 8 struct MixedAlign { diff --git a/nautilus/test/data/value-tracing-tests/after_constant_folding/moveConstructTrivial.nautilus b/nautilus/test/data/value-tracing-tests/after_constant_folding/moveConstructTrivial.nautilus new file mode 100644 index 000000000..6a960586d --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/after_constant_folding/moveConstructTrivial.nautilus @@ -0,0 +1,30 @@ +nautilus { +execute() :i32 { +Block_0(): + $1 = alloca 8b :ptr + $3 = 0 :ui64 + $5 = 1 :ui64 + $6 = 0 :ui64 + $7 = $1 + $6 :ptr + $10 = 42 :i32 + store($10, $7) :void + $15 = 4 :ui64 + $17 = 1 :ui64 + $18 = 4 :ui64 + $19 = $1 + $18 :ptr + $22 = 10 :i32 + store($22, $19) :void + $27 = 0 :ui64 + $29 = 1 :ui64 + $30 = 0 :ui64 + $31 = $1 + $30 :ptr + $35 = 4 :ui64 + $37 = 1 :ui64 + $38 = 4 :ui64 + $39 = $1 + $38 :ptr + $42 = load($31) :i32 + $43 = load($39) :i32 + $44 = $42 + $43 :i32 + return ($44) :i32 +} +} //nautilus diff --git a/nautilus/test/data/value-tracing-tests/after_constant_folding/returnByValue.nautilus b/nautilus/test/data/value-tracing-tests/after_constant_folding/returnByValue.nautilus new file mode 100644 index 000000000..65d4cf963 --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/after_constant_folding/returnByValue.nautilus @@ -0,0 +1,23 @@ +nautilus { +execute($1:i32) :i32 { +Block_0($1:i32): + $3 = alloca 8b :ptr + $7 = 0 :ui64 + $9 = 1 :ui64 + $10 = 0 :ui64 + $11 = $3 + $10 :ptr + store($1, $11) :void + $18 = 4 :ui64 + $20 = 1 :ui64 + $21 = 4 :ui64 + $22 = $3 + $21 :ptr + $25 = 0 :i32 + store($25, $22) :void + $30 = 0 :ui64 + $32 = 1 :ui64 + $33 = 0 :ui64 + $34 = $3 + $33 :ptr + $37 = load($34) :i32 + return ($37) :i32 +} +} //nautilus diff --git a/nautilus/test/data/value-tracing-tests/after_constant_folding/staticLoopAssignStructToOuter.nautilus b/nautilus/test/data/value-tracing-tests/after_constant_folding/staticLoopAssignStructToOuter.nautilus new file mode 100644 index 000000000..9b64d5d5b --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/after_constant_folding/staticLoopAssignStructToOuter.nautilus @@ -0,0 +1,75 @@ +nautilus { +execute() :i32 { +Block_0(): + $1 = alloca 8b :ptr + $26 = alloca 8b :ptr + $53 = alloca 8b :ptr + $80 = alloca 8b :ptr + $3 = 0 :ui64 + $5 = 1 :ui64 + $6 = 0 :ui64 + $7 = $1 + $6 :ptr + $10 = 0 :i32 + store($10, $7) :void + $15 = 4 :ui64 + $17 = 1 :ui64 + $18 = 4 :ui64 + $19 = $1 + $18 :ptr + $22 = 0 :i32 + store($22, $19) :void + $28 = 0 :ui64 + $30 = 1 :ui64 + $31 = 0 :ui64 + $32 = $26 + $31 :ptr + $35 = 1 :i32 + store($35, $32) :void + $40 = 4 :ui64 + $42 = 1 :ui64 + $43 = 4 :ui64 + $44 = $26 + $43 :ptr + $47 = 10 :i32 + store($47, $44) :void + $51 = 8 :ui64 + $52 = func_*($1,$26,$51) :ptr + $55 = 0 :ui64 + $57 = 1 :ui64 + $58 = 0 :ui64 + $59 = $53 + $58 :ptr + $62 = 2 :i32 + store($62, $59) :void + $67 = 4 :ui64 + $69 = 1 :ui64 + $70 = 4 :ui64 + $71 = $53 + $70 :ptr + $74 = 20 :i32 + store($74, $71) :void + $78 = 8 :ui64 + $79 = func_*($1,$53,$78) :ptr + $82 = 0 :ui64 + $84 = 1 :ui64 + $85 = 0 :ui64 + $86 = $80 + $85 :ptr + $89 = 3 :i32 + store($89, $86) :void + $94 = 4 :ui64 + $96 = 1 :ui64 + $97 = 4 :ui64 + $98 = $80 + $97 :ptr + $101 = 30 :i32 + store($101, $98) :void + $105 = 8 :ui64 + $106 = func_*($1,$80,$105) :ptr + $108 = 0 :ui64 + $110 = 1 :ui64 + $111 = 0 :ui64 + $112 = $1 + $111 :ptr + $116 = 4 :ui64 + $118 = 1 :ui64 + $119 = 4 :ui64 + $120 = $1 + $119 :ptr + $123 = load($112) :i32 + $124 = load($120) :i32 + $125 = $123 + $124 :i32 + return ($125) :i32 +} +} //nautilus diff --git a/nautilus/test/data/value-tracing-tests/after_empty_block_elim/moveConstructTrivial.nautilus b/nautilus/test/data/value-tracing-tests/after_empty_block_elim/moveConstructTrivial.nautilus new file mode 100644 index 000000000..3b6a97c56 --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/after_empty_block_elim/moveConstructTrivial.nautilus @@ -0,0 +1,30 @@ +nautilus { +execute() :i32 { +Block_0(): + $1 = alloca 8b :ptr + $3 = 0 :ui64 + $5 = 1 :ui64 + $6 = $3 * $5 :ui64 + $7 = $1 + $6 :ptr + $10 = 42 :i32 + store($10, $7) :void + $15 = 4 :ui64 + $17 = 1 :ui64 + $18 = $15 * $17 :ui64 + $19 = $1 + $18 :ptr + $22 = 10 :i32 + store($22, $19) :void + $27 = 0 :ui64 + $29 = 1 :ui64 + $30 = $27 * $29 :ui64 + $31 = $1 + $30 :ptr + $35 = 4 :ui64 + $37 = 1 :ui64 + $38 = $35 * $37 :ui64 + $39 = $1 + $38 :ptr + $42 = load($31) :i32 + $43 = load($39) :i32 + $44 = $42 + $43 :i32 + return ($44) :i32 +} +} //nautilus diff --git a/nautilus/test/data/value-tracing-tests/after_empty_block_elim/returnByValue.nautilus b/nautilus/test/data/value-tracing-tests/after_empty_block_elim/returnByValue.nautilus new file mode 100644 index 000000000..b3222fd69 --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/after_empty_block_elim/returnByValue.nautilus @@ -0,0 +1,23 @@ +nautilus { +execute($1:i32) :i32 { +Block_0($1:i32): + $3 = alloca 8b :ptr + $7 = 0 :ui64 + $9 = 1 :ui64 + $10 = $7 * $9 :ui64 + $11 = $3 + $10 :ptr + store($1, $11) :void + $18 = 4 :ui64 + $20 = 1 :ui64 + $21 = $18 * $20 :ui64 + $22 = $3 + $21 :ptr + $25 = 0 :i32 + store($25, $22) :void + $30 = 0 :ui64 + $32 = 1 :ui64 + $33 = $30 * $32 :ui64 + $34 = $3 + $33 :ptr + $37 = load($34) :i32 + return ($37) :i32 +} +} //nautilus diff --git a/nautilus/test/data/value-tracing-tests/after_empty_block_elim/staticLoopAssignStructToOuter.nautilus b/nautilus/test/data/value-tracing-tests/after_empty_block_elim/staticLoopAssignStructToOuter.nautilus new file mode 100644 index 000000000..2a323f6bb --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/after_empty_block_elim/staticLoopAssignStructToOuter.nautilus @@ -0,0 +1,75 @@ +nautilus { +execute() :i32 { +Block_0(): + $1 = alloca 8b :ptr + $26 = alloca 8b :ptr + $53 = alloca 8b :ptr + $80 = alloca 8b :ptr + $3 = 0 :ui64 + $5 = 1 :ui64 + $6 = $3 * $5 :ui64 + $7 = $1 + $6 :ptr + $10 = 0 :i32 + store($10, $7) :void + $15 = 4 :ui64 + $17 = 1 :ui64 + $18 = $15 * $17 :ui64 + $19 = $1 + $18 :ptr + $22 = 0 :i32 + store($22, $19) :void + $28 = 0 :ui64 + $30 = 1 :ui64 + $31 = $28 * $30 :ui64 + $32 = $26 + $31 :ptr + $35 = 1 :i32 + store($35, $32) :void + $40 = 4 :ui64 + $42 = 1 :ui64 + $43 = $40 * $42 :ui64 + $44 = $26 + $43 :ptr + $47 = 10 :i32 + store($47, $44) :void + $51 = 8 :ui64 + $52 = func_*($1,$26,$51) :ptr + $55 = 0 :ui64 + $57 = 1 :ui64 + $58 = $55 * $57 :ui64 + $59 = $53 + $58 :ptr + $62 = 2 :i32 + store($62, $59) :void + $67 = 4 :ui64 + $69 = 1 :ui64 + $70 = $67 * $69 :ui64 + $71 = $53 + $70 :ptr + $74 = 20 :i32 + store($74, $71) :void + $78 = 8 :ui64 + $79 = func_*($1,$53,$78) :ptr + $82 = 0 :ui64 + $84 = 1 :ui64 + $85 = $82 * $84 :ui64 + $86 = $80 + $85 :ptr + $89 = 3 :i32 + store($89, $86) :void + $94 = 4 :ui64 + $96 = 1 :ui64 + $97 = $94 * $96 :ui64 + $98 = $80 + $97 :ptr + $101 = 30 :i32 + store($101, $98) :void + $105 = 8 :ui64 + $106 = func_*($1,$80,$105) :ptr + $108 = 0 :ui64 + $110 = 1 :ui64 + $111 = $108 * $110 :ui64 + $112 = $1 + $111 :ptr + $116 = 4 :ui64 + $118 = 1 :ui64 + $119 = $116 * $118 :ui64 + $120 = $1 + $119 :ptr + $123 = load($112) :i32 + $124 = load($120) :i32 + $125 = $123 + $124 :i32 + return ($125) :i32 +} +} //nautilus diff --git a/nautilus/test/data/value-tracing-tests/after_ssa/moveConstructTrivial.trace b/nautilus/test/data/value-tracing-tests/after_ssa/moveConstructTrivial.trace new file mode 100644 index 000000000..046e1531e --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/after_ssa/moveConstructTrivial.trace @@ -0,0 +1,28 @@ +EXECUTE: +B0() + ALLOCA $1 :ptr + CONST $3 0 :ui64 + CONST $5 1 :ui64 + MUL $6 $3 $5 :ui64 + ADD $7 $1 $6 :ptr + CONST $10 42 :i32 + STORE $13 $7 $10 :void + CONST $15 4 :ui64 + CONST $17 1 :ui64 + MUL $18 $15 $17 :ui64 + ADD $19 $1 $18 :ptr + CONST $22 10 :i32 + STORE $25 $19 $22 :void + CONST $27 0 :ui64 + CONST $29 1 :ui64 + MUL $30 $27 $29 :ui64 + ADD $31 $1 $30 :ptr + CONST $35 4 :ui64 + CONST $37 1 :ui64 + MUL $38 $35 $37 :ui64 + ADD $39 $1 $38 :ptr + LOAD $42 $31 :i32 + LOAD $43 $39 :i32 + ADD $44 $42 $43 :i32 + RETURN $0 $44 :i32 + diff --git a/nautilus/test/data/value-tracing-tests/after_ssa/returnByValue.trace b/nautilus/test/data/value-tracing-tests/after_ssa/returnByValue.trace new file mode 100644 index 000000000..50dbc7ef8 --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/after_ssa/returnByValue.trace @@ -0,0 +1,21 @@ +EXECUTE: +B0($1:i32) + ALLOCA $3 :ptr + CONST $7 0 :ui64 + CONST $9 1 :ui64 + MUL $10 $7 $9 :ui64 + ADD $11 $3 $10 :ptr + STORE $16 $11 $1 :void + CONST $18 4 :ui64 + CONST $20 1 :ui64 + MUL $21 $18 $20 :ui64 + ADD $22 $3 $21 :ptr + CONST $25 0 :i32 + STORE $28 $22 $25 :void + CONST $30 0 :ui64 + CONST $32 1 :ui64 + MUL $33 $30 $32 :ui64 + ADD $34 $3 $33 :ptr + LOAD $37 $34 :i32 + RETURN $0 $37 :i32 + diff --git a/nautilus/test/data/value-tracing-tests/after_ssa/staticLoopAssignStructToOuter.trace b/nautilus/test/data/value-tracing-tests/after_ssa/staticLoopAssignStructToOuter.trace new file mode 100644 index 000000000..67be07f29 --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/after_ssa/staticLoopAssignStructToOuter.trace @@ -0,0 +1,73 @@ +EXECUTE: +B0() + ALLOCA $1 :ptr + ALLOCA $26 :ptr + ALLOCA $53 :ptr + ALLOCA $80 :ptr + CONST $3 0 :ui64 + CONST $5 1 :ui64 + MUL $6 $3 $5 :ui64 + ADD $7 $1 $6 :ptr + CONST $10 0 :i32 + STORE $13 $7 $10 :void + CONST $15 4 :ui64 + CONST $17 1 :ui64 + MUL $18 $15 $17 :ui64 + ADD $19 $1 $18 :ptr + CONST $22 0 :i32 + STORE $25 $19 $22 :void + CONST $28 0 :ui64 + CONST $30 1 :ui64 + MUL $31 $28 $30 :ui64 + ADD $32 $26 $31 :ptr + CONST $35 1 :i32 + STORE $38 $32 $35 :void + CONST $40 4 :ui64 + CONST $42 1 :ui64 + MUL $43 $40 $42 :ui64 + ADD $44 $26 $43 :ptr + CONST $47 10 :i32 + STORE $50 $44 $47 :void + CONST $51 8 :ui64 + CALL $52 func_*($1,$26,$51) :ptr + CONST $55 0 :ui64 + CONST $57 1 :ui64 + MUL $58 $55 $57 :ui64 + ADD $59 $53 $58 :ptr + CONST $62 2 :i32 + STORE $65 $59 $62 :void + CONST $67 4 :ui64 + CONST $69 1 :ui64 + MUL $70 $67 $69 :ui64 + ADD $71 $53 $70 :ptr + CONST $74 20 :i32 + STORE $77 $71 $74 :void + CONST $78 8 :ui64 + CALL $79 func_*($1,$53,$78) :ptr + CONST $82 0 :ui64 + CONST $84 1 :ui64 + MUL $85 $82 $84 :ui64 + ADD $86 $80 $85 :ptr + CONST $89 3 :i32 + STORE $92 $86 $89 :void + CONST $94 4 :ui64 + CONST $96 1 :ui64 + MUL $97 $94 $96 :ui64 + ADD $98 $80 $97 :ptr + CONST $101 30 :i32 + STORE $104 $98 $101 :void + CONST $105 8 :ui64 + CALL $106 func_*($1,$80,$105) :ptr + CONST $108 0 :ui64 + CONST $110 1 :ui64 + MUL $111 $108 $110 :ui64 + ADD $112 $1 $111 :ptr + CONST $116 4 :ui64 + CONST $118 1 :ui64 + MUL $119 $116 $118 :ui64 + ADD $120 $1 $119 :ptr + LOAD $123 $112 :i32 + LOAD $124 $120 :i32 + ADD $125 $123 $124 :i32 + RETURN $0 $125 :i32 + diff --git a/nautilus/test/data/value-tracing-tests/ir/moveConstructTrivial.nautilus b/nautilus/test/data/value-tracing-tests/ir/moveConstructTrivial.nautilus new file mode 100644 index 000000000..3b6a97c56 --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/ir/moveConstructTrivial.nautilus @@ -0,0 +1,30 @@ +nautilus { +execute() :i32 { +Block_0(): + $1 = alloca 8b :ptr + $3 = 0 :ui64 + $5 = 1 :ui64 + $6 = $3 * $5 :ui64 + $7 = $1 + $6 :ptr + $10 = 42 :i32 + store($10, $7) :void + $15 = 4 :ui64 + $17 = 1 :ui64 + $18 = $15 * $17 :ui64 + $19 = $1 + $18 :ptr + $22 = 10 :i32 + store($22, $19) :void + $27 = 0 :ui64 + $29 = 1 :ui64 + $30 = $27 * $29 :ui64 + $31 = $1 + $30 :ptr + $35 = 4 :ui64 + $37 = 1 :ui64 + $38 = $35 * $37 :ui64 + $39 = $1 + $38 :ptr + $42 = load($31) :i32 + $43 = load($39) :i32 + $44 = $42 + $43 :i32 + return ($44) :i32 +} +} //nautilus diff --git a/nautilus/test/data/value-tracing-tests/ir/returnByValue.nautilus b/nautilus/test/data/value-tracing-tests/ir/returnByValue.nautilus new file mode 100644 index 000000000..b3222fd69 --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/ir/returnByValue.nautilus @@ -0,0 +1,23 @@ +nautilus { +execute($1:i32) :i32 { +Block_0($1:i32): + $3 = alloca 8b :ptr + $7 = 0 :ui64 + $9 = 1 :ui64 + $10 = $7 * $9 :ui64 + $11 = $3 + $10 :ptr + store($1, $11) :void + $18 = 4 :ui64 + $20 = 1 :ui64 + $21 = $18 * $20 :ui64 + $22 = $3 + $21 :ptr + $25 = 0 :i32 + store($25, $22) :void + $30 = 0 :ui64 + $32 = 1 :ui64 + $33 = $30 * $32 :ui64 + $34 = $3 + $33 :ptr + $37 = load($34) :i32 + return ($37) :i32 +} +} //nautilus diff --git a/nautilus/test/data/value-tracing-tests/ir/staticLoopAssignStructToOuter.nautilus b/nautilus/test/data/value-tracing-tests/ir/staticLoopAssignStructToOuter.nautilus new file mode 100644 index 000000000..2a323f6bb --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/ir/staticLoopAssignStructToOuter.nautilus @@ -0,0 +1,75 @@ +nautilus { +execute() :i32 { +Block_0(): + $1 = alloca 8b :ptr + $26 = alloca 8b :ptr + $53 = alloca 8b :ptr + $80 = alloca 8b :ptr + $3 = 0 :ui64 + $5 = 1 :ui64 + $6 = $3 * $5 :ui64 + $7 = $1 + $6 :ptr + $10 = 0 :i32 + store($10, $7) :void + $15 = 4 :ui64 + $17 = 1 :ui64 + $18 = $15 * $17 :ui64 + $19 = $1 + $18 :ptr + $22 = 0 :i32 + store($22, $19) :void + $28 = 0 :ui64 + $30 = 1 :ui64 + $31 = $28 * $30 :ui64 + $32 = $26 + $31 :ptr + $35 = 1 :i32 + store($35, $32) :void + $40 = 4 :ui64 + $42 = 1 :ui64 + $43 = $40 * $42 :ui64 + $44 = $26 + $43 :ptr + $47 = 10 :i32 + store($47, $44) :void + $51 = 8 :ui64 + $52 = func_*($1,$26,$51) :ptr + $55 = 0 :ui64 + $57 = 1 :ui64 + $58 = $55 * $57 :ui64 + $59 = $53 + $58 :ptr + $62 = 2 :i32 + store($62, $59) :void + $67 = 4 :ui64 + $69 = 1 :ui64 + $70 = $67 * $69 :ui64 + $71 = $53 + $70 :ptr + $74 = 20 :i32 + store($74, $71) :void + $78 = 8 :ui64 + $79 = func_*($1,$53,$78) :ptr + $82 = 0 :ui64 + $84 = 1 :ui64 + $85 = $82 * $84 :ui64 + $86 = $80 + $85 :ptr + $89 = 3 :i32 + store($89, $86) :void + $94 = 4 :ui64 + $96 = 1 :ui64 + $97 = $94 * $96 :ui64 + $98 = $80 + $97 :ptr + $101 = 30 :i32 + store($101, $98) :void + $105 = 8 :ui64 + $106 = func_*($1,$80,$105) :ptr + $108 = 0 :ui64 + $110 = 1 :ui64 + $111 = $108 * $110 :ui64 + $112 = $1 + $111 :ptr + $116 = 4 :ui64 + $118 = 1 :ui64 + $119 = $116 * $118 :ui64 + $120 = $1 + $119 :ptr + $123 = load($112) :i32 + $124 = load($120) :i32 + $125 = $123 + $124 :i32 + return ($125) :i32 +} +} //nautilus diff --git a/nautilus/test/data/value-tracing-tests/tracing/moveConstructTrivial.trace b/nautilus/test/data/value-tracing-tests/tracing/moveConstructTrivial.trace new file mode 100644 index 000000000..ce7328643 --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/tracing/moveConstructTrivial.trace @@ -0,0 +1,48 @@ +EXECUTE: +B0() + ALLOCA $1 :ptr + ASSIGN $2 $1 :ptr + CONST $3 0 :ui64 + ASSIGN $4 $3 :ui64 + CONST $5 1 :ui64 + MUL $6 $4 $5 :ui64 + ADD $7 $2 $6 :ptr + ASSIGN $8 $7 :ptr + ASSIGN $9 $8 :ptr + CONST $10 42 :i32 + ASSIGN $11 $10 :i32 + ASSIGN $12 $11 :i32 + STORE $13 $9 $12 :void + ASSIGN $14 $1 :ptr + CONST $15 4 :ui64 + ASSIGN $16 $15 :ui64 + CONST $17 1 :ui64 + MUL $18 $16 $17 :ui64 + ADD $19 $14 $18 :ptr + ASSIGN $20 $19 :ptr + ASSIGN $21 $20 :ptr + CONST $22 10 :i32 + ASSIGN $23 $22 :i32 + ASSIGN $24 $23 :i32 + STORE $25 $21 $24 :void + ASSIGN $26 $1 :ptr + CONST $27 0 :ui64 + ASSIGN $28 $27 :ui64 + CONST $29 1 :ui64 + MUL $30 $28 $29 :ui64 + ADD $31 $26 $30 :ptr + ASSIGN $32 $31 :ptr + ASSIGN $33 $32 :ptr + ASSIGN $34 $1 :ptr + CONST $35 4 :ui64 + ASSIGN $36 $35 :ui64 + CONST $37 1 :ui64 + MUL $38 $36 $37 :ui64 + ADD $39 $34 $38 :ptr + ASSIGN $40 $39 :ptr + ASSIGN $41 $40 :ptr + LOAD $42 $33 :i32 + LOAD $43 $41 :i32 + ADD $44 $42 $43 :i32 + RETURN $0 $44 :i32 + diff --git a/nautilus/test/data/value-tracing-tests/tracing/returnByValue.trace b/nautilus/test/data/value-tracing-tests/tracing/returnByValue.trace new file mode 100644 index 000000000..38be1cc34 --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/tracing/returnByValue.trace @@ -0,0 +1,40 @@ +EXECUTE: +B0($1:i32) + ASSIGN $2 $1 :i32 + ALLOCA $3 :ptr + ASSIGN $4 $2 :i32 + ASSIGN $5 $4 :i32 + ASSIGN $6 $3 :ptr + CONST $7 0 :ui64 + ASSIGN $8 $7 :ui64 + CONST $9 1 :ui64 + MUL $10 $8 $9 :ui64 + ADD $11 $6 $10 :ptr + ASSIGN $12 $11 :ptr + ASSIGN $13 $12 :ptr + ASSIGN $14 $5 :i32 + ASSIGN $15 $14 :i32 + STORE $16 $13 $15 :void + ASSIGN $17 $3 :ptr + CONST $18 4 :ui64 + ASSIGN $19 $18 :ui64 + CONST $20 1 :ui64 + MUL $21 $19 $20 :ui64 + ADD $22 $17 $21 :ptr + ASSIGN $23 $22 :ptr + ASSIGN $24 $23 :ptr + CONST $25 0 :i32 + ASSIGN $26 $25 :i32 + ASSIGN $27 $26 :i32 + STORE $28 $24 $27 :void + ASSIGN $29 $3 :ptr + CONST $30 0 :ui64 + ASSIGN $31 $30 :ui64 + CONST $32 1 :ui64 + MUL $33 $31 $32 :ui64 + ADD $34 $29 $33 :ptr + ASSIGN $35 $34 :ptr + ASSIGN $36 $35 :ptr + LOAD $37 $36 :i32 + RETURN $0 $37 :i32 + diff --git a/nautilus/test/data/value-tracing-tests/tracing/staticLoopAssignStructToOuter.trace b/nautilus/test/data/value-tracing-tests/tracing/staticLoopAssignStructToOuter.trace new file mode 100644 index 000000000..d0e6aa1e2 --- /dev/null +++ b/nautilus/test/data/value-tracing-tests/tracing/staticLoopAssignStructToOuter.trace @@ -0,0 +1,129 @@ +EXECUTE: +B0() + ALLOCA $1 :ptr + ASSIGN $2 $1 :ptr + CONST $3 0 :ui64 + ASSIGN $4 $3 :ui64 + CONST $5 1 :ui64 + MUL $6 $4 $5 :ui64 + ADD $7 $2 $6 :ptr + ASSIGN $8 $7 :ptr + ASSIGN $9 $8 :ptr + CONST $10 0 :i32 + ASSIGN $11 $10 :i32 + ASSIGN $12 $11 :i32 + STORE $13 $9 $12 :void + ASSIGN $14 $1 :ptr + CONST $15 4 :ui64 + ASSIGN $16 $15 :ui64 + CONST $17 1 :ui64 + MUL $18 $16 $17 :ui64 + ADD $19 $14 $18 :ptr + ASSIGN $20 $19 :ptr + ASSIGN $21 $20 :ptr + CONST $22 0 :i32 + ASSIGN $23 $22 :i32 + ASSIGN $24 $23 :i32 + STORE $25 $21 $24 :void + ALLOCA $26 :ptr + ASSIGN $27 $26 :ptr + CONST $28 0 :ui64 + ASSIGN $29 $28 :ui64 + CONST $30 1 :ui64 + MUL $31 $29 $30 :ui64 + ADD $32 $27 $31 :ptr + ASSIGN $33 $32 :ptr + ASSIGN $34 $33 :ptr + CONST $35 1 :i32 + ASSIGN $36 $35 :i32 + ASSIGN $37 $36 :i32 + STORE $38 $34 $37 :void + ASSIGN $39 $26 :ptr + CONST $40 4 :ui64 + ASSIGN $41 $40 :ui64 + CONST $42 1 :ui64 + MUL $43 $41 $42 :ui64 + ADD $44 $39 $43 :ptr + ASSIGN $45 $44 :ptr + ASSIGN $46 $45 :ptr + CONST $47 10 :i32 + ASSIGN $48 $47 :i32 + ASSIGN $49 $48 :i32 + STORE $50 $46 $49 :void + CONST $51 8 :ui64 + CALL $52 func_*($1,$26,$51) :ptr + ALLOCA $53 :ptr + ASSIGN $54 $53 :ptr + CONST $55 0 :ui64 + ASSIGN $56 $55 :ui64 + CONST $57 1 :ui64 + MUL $58 $56 $57 :ui64 + ADD $59 $54 $58 :ptr + ASSIGN $60 $59 :ptr + ASSIGN $61 $60 :ptr + CONST $62 2 :i32 + ASSIGN $63 $62 :i32 + ASSIGN $64 $63 :i32 + STORE $65 $61 $64 :void + ASSIGN $66 $53 :ptr + CONST $67 4 :ui64 + ASSIGN $68 $67 :ui64 + CONST $69 1 :ui64 + MUL $70 $68 $69 :ui64 + ADD $71 $66 $70 :ptr + ASSIGN $72 $71 :ptr + ASSIGN $73 $72 :ptr + CONST $74 20 :i32 + ASSIGN $75 $74 :i32 + ASSIGN $76 $75 :i32 + STORE $77 $73 $76 :void + CONST $78 8 :ui64 + CALL $79 func_*($1,$53,$78) :ptr + ALLOCA $80 :ptr + ASSIGN $81 $80 :ptr + CONST $82 0 :ui64 + ASSIGN $83 $82 :ui64 + CONST $84 1 :ui64 + MUL $85 $83 $84 :ui64 + ADD $86 $81 $85 :ptr + ASSIGN $87 $86 :ptr + ASSIGN $88 $87 :ptr + CONST $89 3 :i32 + ASSIGN $90 $89 :i32 + ASSIGN $91 $90 :i32 + STORE $92 $88 $91 :void + ASSIGN $93 $80 :ptr + CONST $94 4 :ui64 + ASSIGN $95 $94 :ui64 + CONST $96 1 :ui64 + MUL $97 $95 $96 :ui64 + ADD $98 $93 $97 :ptr + ASSIGN $99 $98 :ptr + ASSIGN $100 $99 :ptr + CONST $101 30 :i32 + ASSIGN $102 $101 :i32 + ASSIGN $103 $102 :i32 + STORE $104 $100 $103 :void + CONST $105 8 :ui64 + CALL $106 func_*($1,$80,$105) :ptr + ASSIGN $107 $1 :ptr + CONST $108 0 :ui64 + ASSIGN $109 $108 :ui64 + CONST $110 1 :ui64 + MUL $111 $109 $110 :ui64 + ADD $112 $107 $111 :ptr + ASSIGN $113 $112 :ptr + ASSIGN $114 $113 :ptr + ASSIGN $115 $1 :ptr + CONST $116 4 :ui64 + ASSIGN $117 $116 :ui64 + CONST $118 1 :ui64 + MUL $119 $117 $118 :ui64 + ADD $120 $115 $119 :ptr + ASSIGN $121 $120 :ptr + ASSIGN $122 $121 :ptr + LOAD $123 $114 :i32 + LOAD $124 $122 :i32 + ADD $125 $123 $124 :i32 + RETURN $0 $125 :i32 + diff --git a/nautilus/test/execution-tests/ExecutionTest.cpp b/nautilus/test/execution-tests/ExecutionTest.cpp index c603fc50a..1a8ecf11b 100644 --- a/nautilus/test/execution-tests/ExecutionTest.cpp +++ b/nautilus/test/execution-tests/ExecutionTest.cpp @@ -975,6 +975,47 @@ void valueExecutionTest(engine::NautilusEngine& engine) { auto f = engine.registerFunction(copyConstructNonTrivial); REQUIRE(f() == 99); } + SECTION("moveConstructTrivial") { + auto f = engine.registerFunction(moveConstructTrivial); + REQUIRE(f() == 52); + } + SECTION("moveConstructNonTrivial") { + auto f = engine.registerFunction(moveConstructNonTrivial); + REQUIRE(f() == 99); + } + SECTION("moveAssign") { + auto f = engine.registerFunction(moveAssign); + REQUIRE(f() == 1); + } + SECTION("moveAssignSelf") { + auto f = engine.registerFunction(moveAssignSelf); + REQUIRE(f() == 5); + } + SECTION("moveDtorBalance") { + CountedDtor::ctor_count = 0; + CountedDtor::dtor_count = 0; + auto f = engine.registerFunction(moveDtorBalance); + REQUIRE(f() == 7); + REQUIRE(CountedDtor::ctor_count >= 1); + REQUIRE(CountedDtor::ctor_count == CountedDtor::dtor_count); + } + SECTION("returnByValue") { + auto f = engine.registerFunction(returnByValue); + REQUIRE(f((int32_t) 0) == 0); + REQUIRE(f((int32_t) 42) == 42); + REQUIRE(f((int32_t) -7) == -7); + } + SECTION("returnByValueCond") { + auto f = engine.registerFunction(returnByValueCond); + REQUIRE(f((int32_t) 1) == 1); + REQUIRE(f((int32_t) 0) == 2); + REQUIRE(f((int32_t) -3) == 2); + } + SECTION("staticLoopAssignStructToOuter") { + auto f = engine.registerFunction(staticLoopAssignStructToOuter); + // Final iteration writes inner.a=3, inner.b=30 into outer; sum is 33. + REQUIRE(f() == 33); + } SECTION("nonTrivialDestructor") { auto f = engine.registerFunction(nonTrivialDestructor); REQUIRE(f() == 42); diff --git a/nautilus/test/execution-tests/TracingTest.cpp b/nautilus/test/execution-tests/TracingTest.cpp index 13708f165..fb25f8484 100644 --- a/nautilus/test/execution-tests/TracingTest.cpp +++ b/nautilus/test/execution-tests/TracingTest.cpp @@ -343,6 +343,11 @@ TEST_CASE("Value Trace Test") { {"copyConstruct", details::createFunctionWrapper(copyConstruct)}, {"copyAssign", details::createFunctionWrapper(copyAssign)}, {"copyConstructNonTrivial", details::createFunctionWrapper(copyConstructNonTrivial)}, + // move constructor (no traceCopy/ASSIGN op should appear for the move itself) + {"moveConstructTrivial", details::createFunctionWrapper(moveConstructTrivial)}, + {"returnByValue", details::createFunctionWrapper(returnByValue)}, + // static (fully-unrolled) loop with per-iteration alloca assigned into an outer struct + {"staticLoopAssignStructToOuter", details::createFunctionWrapper(staticLoopAssignStructToOuter)}, // destructor {"nonTrivialDestructor", details::createFunctionWrapper(nonTrivialDestructor)}, // loops