diff --git a/include/lauf/asm/builder.h b/include/lauf/asm/builder.h index 474f874..f60c8a8 100644 --- a/include/lauf/asm/builder.h +++ b/include/lauf/asm/builder.h @@ -18,6 +18,11 @@ typedef struct lauf_asm_type lauf_asm_type; typedef struct lauf_asm_layout lauf_asm_layout; typedef struct lauf_runtime_builtin lauf_runtime_builtin_function; +typedef struct lauf_asm_value +{ + uint32_t _id; +} lauf_asm_value; + typedef enum lauf_asm_inst_condition_code { LAUF_ASM_INST_CC_EQ, @@ -249,6 +254,15 @@ void lauf_asm_inst_layout(lauf_asm_builder* b, lauf_asm_layout layout); void lauf_asm_inst_cc(lauf_asm_builder* b, lauf_asm_inst_condition_code cc); //=== stack manipulation instructions ===// +/// Returns a stable id for the value at the given stack idx. +/// As long as the corresponding value remains on the stack, it can be referenced using that id, +/// even if it is shifted around on the stack due to other stack manipulation instructions. +lauf_asm_value lauf_asm_inst_value(lauf_asm_builder* b, uint16_t stack_index); + +/// Returns the stack index of the value with the given id. +/// The value must still be on the stack. +uint16_t lauf_asm_inst_value_stack_index(lauf_asm_builder* b, lauf_asm_value value); + /// Pops the Nth value of the stack. /// /// Signature: x_N+1 x_N x_N-1 ... x_0 => x_N+1 x_N-1 ... x_0 diff --git a/src/lauf/asm/builder.cpp b/src/lauf/asm/builder.cpp index 520dd39..e244cdc 100644 --- a/src/lauf/asm/builder.cpp +++ b/src/lauf/asm/builder.cpp @@ -740,7 +740,7 @@ void lauf_asm_inst_call(lauf_asm_builder* b, const lauf_asm_function* callee) auto offset = lauf::compress_pointer_offset(b->fn, callee); b->cur->insts.push_back(*b, LAUF_BUILD_INST_OFFSET(call, offset)); - b->cur->vstack.push(*b, callee->sig.output_count); + b->cur->vstack.push_output(*b, callee->sig.output_count); } namespace @@ -784,7 +784,7 @@ void lauf_asm_inst_call_indirect(lauf_asm_builder* b, lauf_asm_signature sig) sig.output_count, 0)); } - b->cur->vstack.push(*b, sig.output_count); + b->cur->vstack.push_output(*b, sig.output_count); } namespace @@ -801,7 +801,7 @@ void add_call_builtin(lauf_asm_builder* b, lauf_runtime_builtin_function callee) b->cur->insts.push_back(*b, LAUF_BUILD_INST_SIGNATURE(call_builtin_sig, callee.input_count, callee.output_count, callee.flags)); - b->cur->vstack.push(*b, callee.output_count); + b->cur->vstack.push_output(*b, callee.output_count); } } // namespace @@ -910,7 +910,7 @@ void lauf_asm_inst_fiber_resume(lauf_asm_builder* b, lauf_asm_signature sig) LAUF_BUILD_ASSERT(b->cur->vstack.pop(), "missing handle"); b->cur->insts.push_back(*b, LAUF_BUILD_INST_SIGNATURE(fiber_resume, sig.input_count, sig.output_count, 0)); - b->cur->vstack.push(*b, sig.output_count); + b->cur->vstack.push_output(*b, sig.output_count); } void lauf_asm_inst_fiber_transfer(lauf_asm_builder* b, lauf_asm_signature sig) @@ -921,7 +921,7 @@ void lauf_asm_inst_fiber_transfer(lauf_asm_builder* b, lauf_asm_signature sig) LAUF_BUILD_ASSERT(b->cur->vstack.pop(), "missing handle"); b->cur->insts.push_back(*b, LAUF_BUILD_INST_SIGNATURE(fiber_transfer, sig.input_count, sig.output_count, 0)); - b->cur->vstack.push(*b, sig.output_count); + b->cur->vstack.push_output(*b, sig.output_count); } void lauf_asm_inst_fiber_suspend(lauf_asm_builder* b, lauf_asm_signature sig) @@ -931,7 +931,7 @@ void lauf_asm_inst_fiber_suspend(lauf_asm_builder* b, lauf_asm_signature sig) LAUF_BUILD_ASSERT(b->cur->vstack.pop(sig.input_count), "missing inputs"); b->cur->insts.push_back(*b, LAUF_BUILD_INST_SIGNATURE(fiber_suspend, sig.input_count, sig.output_count, 0)); - b->cur->vstack.push(*b, sig.output_count); + b->cur->vstack.push_output(*b, sig.output_count); } void lauf_asm_inst_uint(lauf_asm_builder* b, lauf_uint value) @@ -966,7 +966,7 @@ void lauf_asm_inst_uint(lauf_asm_builder* b, lauf_uint value) b->cur->insts.push_back(*b, LAUF_BUILD_INST_VALUE(push3, value >> 48)); } - b->cur->vstack.push(*b, [&] { + b->cur->vstack.push_constant(*b, [&] { lauf_runtime_value result; result.as_uint = value; return result; @@ -996,7 +996,7 @@ void lauf_asm_inst_null(lauf_asm_builder* b) // NULL has all bits set. b->cur->insts.push_back(*b, LAUF_BUILD_INST_VALUE(pushn, 0)); - b->cur->vstack.push(*b, lauf_runtime_value{}); + b->cur->vstack.push_constant(*b, lauf_runtime_value{}); } void lauf_asm_inst_global_addr(lauf_asm_builder* b, const lauf_asm_global* global) @@ -1004,7 +1004,7 @@ void lauf_asm_inst_global_addr(lauf_asm_builder* b, const lauf_asm_global* globa LAUF_BUILD_CHECK_CUR; b->cur->insts.push_back(*b, LAUF_BUILD_INST_VALUE(global_addr, global->allocation_idx)); - b->cur->vstack.push(*b, [&] { + b->cur->vstack.push_constant(*b, [&] { lauf_runtime_value result; result.as_address.allocation = global->allocation_idx; result.as_address.offset = 0; @@ -1034,7 +1034,7 @@ void lauf_asm_inst_function_addr(lauf_asm_builder* b, const lauf_asm_function* f auto offset = lauf::compress_pointer_offset(b->fn, function); b->cur->insts.push_back(*b, LAUF_BUILD_INST_OFFSET(function_addr, offset)); - b->cur->vstack.push(*b, [&] { + b->cur->vstack.push_constant(*b, [&] { lauf_runtime_value result; result.as_function_address.index = function->function_idx; result.as_function_address.input_count = function->sig.input_count; @@ -1101,15 +1101,37 @@ void lauf_asm_inst_cc(lauf_asm_builder* b, lauf_asm_inst_condition_code cc) add_pop_top_n(b, 1); b->cur->insts.push_back(*b, LAUF_BUILD_INST_VALUE(push, value.as_uint)); - b->cur->vstack.push(*b, value); + b->cur->vstack.push_constant(*b, value); } else { b->cur->insts.push_back(*b, LAUF_BUILD_INST_VALUE(cc, unsigned(cc))); - b->cur->vstack.push(*b, 1); + b->cur->vstack.push_output(*b, 1); } } +lauf_asm_value lauf_asm_inst_value(lauf_asm_builder* b, uint16_t stack_index) +{ + if (LAUF_UNLIKELY(b->cur == nullptr)) + return lauf::invalid_asm_value; + LAUF_BUILD_ASSERT(stack_index < b->cur->vstack.size(), "invalid stack index"); + + auto& id = b->cur->vstack.id(stack_index); + if (id == lauf::invalid_asm_value) + id = b->allocate_value_id(); + return id; +} + +uint16_t lauf_asm_inst_value_stack_index(lauf_asm_builder* b, lauf_asm_value value) +{ + if (LAUF_UNLIKELY(b->cur == nullptr)) + return UINT16_MAX; + + auto idx = b->cur->vstack.find_stack_idx_of(value); + LAUF_BUILD_ASSERT(idx, "value is already popped from the stack"); + return *idx; +} + void lauf_asm_inst_pop(lauf_asm_builder* b, uint16_t stack_index) { LAUF_BUILD_CHECK_CUR; @@ -1167,7 +1189,7 @@ void lauf_asm_inst_select(lauf_asm_builder* b, uint16_t count) LAUF_BUILD_ASSERT(b->cur->vstack.pop(count), "missing alternative values"); b->cur->insts.push_back(*b, LAUF_BUILD_INST_STACK_IDX(select, uint16_t(count - 1))); - b->cur->vstack.push(*b, 1); + b->cur->vstack.push_output(*b, 1); } void lauf_asm_inst_array_element(lauf_asm_builder* b, lauf_asm_layout element_layout) @@ -1187,12 +1209,12 @@ void lauf_asm_inst_array_element(lauf_asm_builder* b, lauf_asm_layout element_la auto offset = index->as_constant.as_sint * lauf_sint(multiple); if (offset > 0) b->cur->insts.push_back(*b, LAUF_BUILD_INST_VALUE(aggregate_member, lauf_uint(offset))); - b->cur->vstack.push(*b, 1); + b->cur->vstack.push_output(*b, 1); } else { b->cur->insts.push_back(*b, LAUF_BUILD_INST_VALUE(array_element, multiple)); - b->cur->vstack.push(*b, 1); + b->cur->vstack.push_output(*b, 1); } } @@ -1212,7 +1234,7 @@ void lauf_asm_inst_aggregate_member(lauf_asm_builder* b, size_t member_index, { LAUF_BUILD_ASSERT(b->cur->vstack.pop(1), "missing address"); b->cur->insts.push_back(*b, LAUF_BUILD_INST_VALUE(aggregate_member, offset)); - b->cur->vstack.push(*b, 1); + b->cur->vstack.push_output(*b, 1); } } @@ -1287,14 +1309,14 @@ void lauf_asm_inst_load_field(lauf_asm_builder* b, lauf_asm_type type, size_t fi b->cur->insts.push_back(*b, LAUF_BUILD_INST_LOCAL_ADDR(load_local_value, addr->as_local->index, addr->as_local->offset)); - b->cur->vstack.push(*b, 1); + b->cur->vstack.push_output(*b, 1); } else if (constant_folding == load_store_global) { add_pop_top_n(b, 1); b->cur->insts.push_back(*b, LAUF_BUILD_INST_VALUE(load_global_value, addr->as_constant.as_address.allocation)); - b->cur->vstack.push(*b, 1); + b->cur->vstack.push_output(*b, 1); } else if (type.layout.size == 0 && type.load_fn == nullptr) { @@ -1304,7 +1326,7 @@ void lauf_asm_inst_load_field(lauf_asm_builder* b, lauf_asm_type type, size_t fi else { b->cur->insts.push_back(*b, LAUF_BUILD_INST_LAYOUT(deref_const, type.layout)); - b->cur->vstack.push(*b, 1); + b->cur->vstack.push_output(*b, 1); lauf_asm_inst_uint(b, field_index); @@ -1348,7 +1370,7 @@ void lauf_asm_inst_store_field(lauf_asm_builder* b, lauf_asm_type type, size_t f else { b->cur->insts.push_back(*b, LAUF_BUILD_INST_LAYOUT(deref_mut, type.layout)); - b->cur->vstack.push(*b, 1); + b->cur->vstack.push_output(*b, 1); lauf_asm_inst_uint(b, field_index); diff --git a/src/lauf/asm/builder.hpp b/src/lauf/asm/builder.hpp index 58834fd..946e739 100644 --- a/src/lauf/asm/builder.hpp +++ b/src/lauf/asm/builder.hpp @@ -10,23 +10,36 @@ #include #include #include +#include #include #include //=== vstack ===// +namespace +{ +constexpr bool operator==(lauf_asm_value lhs, lauf_asm_value rhs) noexcept +{ + return lhs._id == rhs._id; +} +} // namespace + namespace lauf { +constexpr lauf_asm_value invalid_asm_value = {._id = uint32_t(-1)}; + class builder_vstack { public: struct value { - enum + enum type_t { unknown, constant, local_addr, - } type; + }; + type_t type = unknown; + lauf_asm_value id = invalid_asm_value; union { char as_unknown; @@ -38,7 +51,7 @@ class builder_vstack explicit builder_vstack(arena_base& arena, std::size_t input_count) : _max(input_count) { for (auto i = 0u; i != input_count; ++i) - _stack.push_back(arena, {value::unknown, {}}); + _stack.push_back(arena, {}); } std::size_t size() const @@ -56,12 +69,12 @@ class builder_vstack if (size() > _max) _max = size(); } - void push(arena_base& arena, std::size_t n) + void push_output(arena_base& arena, std::size_t n) { for (auto i = 0u; i != n; ++i) - push(arena, {value::unknown, {}}); + push(arena, {}); } - void push(arena_base& arena, lauf_runtime_value constant) + void push_constant(arena_base& arena, lauf_runtime_value constant) { value v; v.type = value::constant; @@ -117,6 +130,26 @@ class builder_vstack *cur = save; } + lauf_asm_value& id(std::size_t stack_idx) + { + return _stack.back(stack_idx).id; + } + + std::optional find_stack_idx_of(lauf_asm_value id) const + { + auto cur = _stack.end(); + --cur; + for (std::uint16_t i = 0u; i != _stack.size(); ++i) + { + if (cur->id == id) + return i; + + --cur; + } + + return std::nullopt; + } + bool finish(uint8_t& output_count) { if (size() > UCHAR_MAX) @@ -184,6 +217,8 @@ struct lauf_asm_builder : lauf::intrinsic_arena // Number of local_addr instructions. std::uint16_t local_addr_count = 0; + lauf_asm_value next_value = {0}; + bool errored = false; explicit lauf_asm_builder(lauf::arena_key key, lauf_asm_build_options options) @@ -207,8 +242,15 @@ struct lauf_asm_builder : lauf::intrinsic_arena local_allocation_size = 0; local_addr_count = 0; + next_value._id = 0; + errored = false; } + + lauf_asm_value allocate_value_id() noexcept + { + return {next_value._id++}; + } }; //=== assertions ===// diff --git a/src/lauf/frontend/text.cpp b/src/lauf/frontend/text.cpp index 9f154f1..c8c1f3c 100644 --- a/src/lauf/frontend/text.cpp +++ b/src/lauf/frontend/text.cpp @@ -74,6 +74,7 @@ struct parse_state symbol_table functions; symbol_table blocks; symbol_table locals; + symbol_table values; lexy::input_location_anchor> anchor; @@ -276,6 +277,17 @@ struct local_ref return *result; }); }; +struct value_ref +{ + static constexpr auto rule = dsl::position(dsl::p); + static constexpr auto value + = callback([](const parse_state& state, auto pos, const std::string& name) { + auto result = state.values.try_lookup(name); + if (result == nullptr) + state.unknown_identifier(pos, "value", name.c_str()); + return *result; + }); +}; struct function_ref { static constexpr auto rule @@ -553,17 +565,38 @@ struct inst_cc static constexpr auto value = inst(&lauf_asm_inst_cc); }; +struct inst_let +{ + static constexpr auto rule + = LAUF_KEYWORD("let") >> dsl::position + dsl::p + + dsl::if_(dsl::equal_sign >> dsl::integer); + static constexpr auto value = callback( + [](parse_state& state, auto pos, const std::string& name, std::uint16_t idx = 0) { + auto value = lauf_asm_inst_value(state.builder, idx); + if (!state.values.insert(name, value)) + state.duplicate_declaration(pos, "value", name.c_str()); + }); +}; struct inst_stack_op { static constexpr auto insts = lexy::symbol_table .map(LEXY_LIT("pop"), &lauf_asm_inst_pop) .map(LEXY_LIT("pick"), &lauf_asm_inst_pick) - .map(LEXY_LIT("roll"), &lauf_asm_inst_roll) - .map(LEXY_LIT("select"), &lauf_asm_inst_select); + .map(LEXY_LIT("roll"), &lauf_asm_inst_roll); - static constexpr auto rule - = dsl::symbol(identifier::unquoted) >> dsl::integer; - static constexpr auto value = inst(); + static constexpr auto rule = dsl::symbol(identifier::unquoted) + >> (dsl::integer | dsl::p); + static constexpr auto value + = callback([](const parse_state& state, auto fn, + std::uint16_t idx) { fn(state.builder, idx); }, + [](const parse_state& state, auto fn, lauf_asm_value value) { + fn(state.builder, lauf_asm_inst_value_stack_index(state.builder, value)); + }); +}; +struct inst_select +{ + static constexpr auto rule = LAUF_KEYWORD("select") >> dsl::integer; + static constexpr auto value = inst(&lauf_asm_inst_select); }; struct inst_call @@ -653,7 +686,7 @@ struct instruction | dsl::p | dsl::p // | dsl::p | dsl::p | dsl::p // | dsl::p | dsl::p | dsl::p // - | dsl::p // + | dsl::p | dsl::p | dsl::p // | dsl::p | dsl::p | dsl::p // | dsl::p | dsl::p // @@ -737,6 +770,7 @@ struct function_decl lauf_asm_build(state.builder, state.mod, state.fn); state.blocks.clear(); state.locals.clear(); + state.values.clear(); }); }; diff --git a/tests/integration/vstack.lauf b/tests/integration/vstack.lauf index c5fdae1..eb56c9f 100644 --- a/tests/integration/vstack.lauf +++ b/tests/integration/vstack.lauf @@ -130,6 +130,22 @@ function @select(1 => 1) { return; } +function @let() { + uint 0; let %a; + uint 1; let %b; + uint 2; let %c; + + pick %c; uint 2; $lauf.test.assert_eq; + pick %b; uint 1; $lauf.test.assert_eq; + pick %a; uint 0; $lauf.test.assert_eq; + + roll %c; uint 2; $lauf.test.assert_eq; + roll %a; uint 0; $lauf.test.assert_eq; + roll %b; uint 1; $lauf.test.assert_eq; + + return; +} + function @main(0 => 1) export { call @pop0; call @pop1; @@ -146,6 +162,8 @@ function @main(0 => 1) export { [ uint 0; call @select; ] uint 1; $lauf.test.assert_eq; [ uint 1; call @select; ] uint 0; $lauf.test.assert_eq; + call @let; + uint 0; return; }