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
68 changes: 68 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,74 @@ jobs:
exit 1
fi

check-super-insts-usage:
runs-on: ubuntu-24.04
name: Check Super Instructions usage
needs: [ check ]

steps:
- uses: actions/checkout@v5
with:
submodules: recursive

- name: Setup compilers, dependencies, project and build
uses: ./.github/workflows/setup-compilers
with:
os_name: ubuntu-24.04
compiler: clang
compiler_version: 16
sanitizers: "On"
with_deps: false

- uses: actions/setup-python@v6
if: ${{ ! startsWith(matrix.config.name, 'Windows') }}
with:
python-version: '3.13'

- run: |
KO=0
python3 tools/ark_frequent_instructions.py super_insts_usage > output.txt || KO=1
echo "SUPER_INSTS_REPORT_KO=$KO" >> $GITHUB_ENV
echo "SUPER_INSTS_REPORT<<EOF" >> $GITHUB_ENV
cat output.txt >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV

- uses: 8BitJonny/gh-get-current-pr@4.0.0
id: PR
with:
sha: ${{ github.event.pull_request.head.sha }}
# Only return if PR is still open
filterOutClosed: true

- run: |
echo "PR found: ${prFound}"
echo "PR ${prNumber:-ERROR} ${prTitle:-NOTITLE}"
env:
# Direct access to common PR properties
prNumber: ${{ steps.PR.outputs.number }}
prTitle: ${{ steps.PR.outputs.pr_title }}
prFound: ${{ steps.PR.outputs.pr_found }}

- name: Find Comment
uses: peter-evans/find-comment@v4
if: steps.PR.outputs.pr_found == 'true'
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: Super Instructions report

- name: Create or update comment
uses: peter-evans/create-or-update-comment@v5.0.0
if: steps.PR.outputs.pr_found == 'true'
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
## Super Instructions report
${{ env.SUPER_INSTS_REPORT }}
edit-mode: replace

build:
runs-on: ${{ matrix.config.os }}
name: ${{ matrix.config.name }}
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Change Log

## [Unreleased changes] - 2026-MM-DD
### Breaking changes
- in function calls, the function to call is now always evaluated first
- in function calls, the arguments are now evaluated from left to right

### Deprecations

### Added
- the bytecode reader can print the argument of a `PUSH_RETURN_ADDRESS` instruction as a hex number
- new super instruction `CALL_SYMBOL_BY_INDEX` to optimise a `LOAD_FAST_BY_INDEX` followed by a `CALL`

### Changed
- instruction counter in the bytecode reader are displayed in hex, and count each instruction instead of each byte

### Removed
- removed a nearly never emitted `GET_CURRENT_PAGE_ADDR` instruction, since it's now always optimised with `CALL` into a `CALL_CURRENT_PAGE` instruction

## [4.4.0] - 2026-03-13
### Deprecations
- `list:permutations` is deprecated in favor of `list:combinations`
Expand Down
79 changes: 42 additions & 37 deletions include/Ark/Compiler/Instructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,63 +116,63 @@ namespace Ark::internal
// @role Read the field named following the given symbol id (cf symbols table) of a `Closure` stored in TS. Pop TS and push the value of field read on the stack
GET_FIELD = 0x15,

// @args symbol id
// @role Read the field named following the given symbol id (cf symbols table) of a `Closure` stored in TS. Pop TS and push the value of field read on the stack, wrapping it in its closure environment
GET_FIELD_AS_CLOSURE = 0x16,

// @args constant id
// @role Load a plugin dynamically, plugin name is stored as a string in the constants table
PLUGIN = 0x16,
PLUGIN = 0x17,

// @args number of elements
// @role Create a list from the N elements pushed on the stack. Follows the function calling convention
LIST = 0x17,
LIST = 0x18,

// @args number of elements
// @role Append N elements to a list (TS). Elements are stored in TS(1)..TS(N). Follows the function calling convention
APPEND = 0x18,
APPEND = 0x19,

// @args number of elements
// @role Concatenate N lists to a list (TS). Lists to concat to TS are stored in TS(1)..TS(N). Follows the function calling convention
CONCAT = 0x19,
CONCAT = 0x1a,

// @args number of elements
// @role Append N elements to a reference to a list (TS), the list is being mutated in-place, no new object created. Elements are stored in TS(1)..TS(N). Follows the function calling convention
APPEND_IN_PLACE = 0x1a,
APPEND_IN_PLACE = 0x1b,

// @args number of elements
// @role Concatenate N lists to a reference to a list (TS), the list is being mutated in-place, no new object created. Lists to concat to TS are stored in TS(1)..TS(N). Follows the function calling convention
CONCAT_IN_PLACE = 0x1b,
CONCAT_IN_PLACE = 0x1c,

// @role Remove an element from a list (TS), given an index (TS1). Push a new list without the removed element to the stack
POP_LIST = 0x1c,
POP_LIST = 0x1d,

// @role Remove an element from a reference to a list (TS), given an index (TS1). The list is mutated in-place, no new object created
POP_LIST_IN_PLACE = 0x1d,
POP_LIST_IN_PLACE = 0x1e,

// @role Modify a reference to a list or string (TS) by replacing the element at TS1 (must be a number) by the value in TS2. The object is mutated in-place, no new object created
SET_AT_INDEX = 0x1e,
SET_AT_INDEX = 0x1f,

// @role Modify a reference to a list (TS) by replacing TS[TS2][TS1] by the value in TS3. TS[TS2] can be a string (if it is, TS3 must be a string). The object is mutated in-place, no new object created
SET_AT_2_INDEX = 0x1f,
SET_AT_2_INDEX = 0x20,

// @role Remove the top of the stack
POP = 0x20,
POP = 0x21,

// @role Pop the top of the stack, if it's false, jump to an address
SHORTCIRCUIT_AND = 0x21,
SHORTCIRCUIT_AND = 0x22,

// @role Pop the top of the stack, if it's true, jump to an address
SHORTCIRCUIT_OR = 0x22,
SHORTCIRCUIT_OR = 0x23,

// @role Create a new local scope
CREATE_SCOPE = 0x23,
CREATE_SCOPE = 0x24,

// @role Reset the current scope so that it is empty, and jump to a given location
RESET_SCOPE_JUMP = 0x24,
RESET_SCOPE_JUMP = 0x25,

// @role Destroy the last local scope
POP_SCOPE = 0x25,

// @args symbol id (function name)
// @role Push the current page address as a value on the stack
GET_CURRENT_PAGE_ADDR = 0x26,
POP_SCOPE = 0x26,

// @role Pop a List from the stack and a function, and call the function with the given list as arguments
APPLY = 0x27,
Expand Down Expand Up @@ -391,69 +391,73 @@ namespace Ark::internal
// @role Call a symbol by its id in `primary`, with `secondary` arguments
CALL_SYMBOL = 0x62,

// @args symbol index, argument count
// @role Call a symbol by its index in the locals in `primary`, with `secondary` arguments
CALL_SYMBOL_BY_INDEX = 0x63,

// @args symbol id (function name), argument count
// @role Call the current page with `secondary` arguments
CALL_CURRENT_PAGE = 0x63,
CALL_CURRENT_PAGE = 0x64,

// @args symbol id, field id in symbols table
// @role Push the field of a given symbol (which has to be a closure) on the stack
GET_FIELD_FROM_SYMBOL = 0x64,
GET_FIELD_FROM_SYMBOL = 0x65,

// @args symbol index, field id in symbols table
// @role Push the field of a given symbol (which has to be a closure) on the stack
GET_FIELD_FROM_SYMBOL_INDEX = 0x65,
GET_FIELD_FROM_SYMBOL_INDEX = 0x66,

// @args symbol id, symbol id2
// @role Push symbol[symbol2]
AT_SYM_SYM = 0x66,
AT_SYM_SYM = 0x67,

// @args symbol index, symbol index2
// @role Push symbol[symbol2]
AT_SYM_INDEX_SYM_INDEX = 0x67,
AT_SYM_INDEX_SYM_INDEX = 0x68,

// @args symbol index, constant id
// @role Push symbol[constant]
AT_SYM_INDEX_CONST = 0x68,
AT_SYM_INDEX_CONST = 0x69,

// @args symbol id, constant id
// @role Check that the type of symbol is the given constant, push true if so, false otherwise
CHECK_TYPE_OF = 0x69,
CHECK_TYPE_OF = 0x6a,

// @args symbol index, constant id
// @role Check that the type of symbol is the given constant, push true if so, false otherwise
CHECK_TYPE_OF_BY_INDEX = 0x6a,
CHECK_TYPE_OF_BY_INDEX = 0x6b,

// @args symbol id, number of elements
// @role Append N elements to a reference to a list (symbol id), the list is being mutated in-place, no new object created. Elements are stored in TS(1)..TS(N). Follows the function calling convention
APPEND_IN_PLACE_SYM = 0x6b,
APPEND_IN_PLACE_SYM = 0x6c,

// @args symbol index, number of elements
// @role Append N elements to a reference to a list (symbol index), the list is being mutated in-place, no new object created. Elements are stored in TS(1)..TS(N). Follows the function calling convention
APPEND_IN_PLACE_SYM_INDEX = 0x6c,
APPEND_IN_PLACE_SYM_INDEX = 0x6d,

// @args symbol index, symbol id
// @role Compute the length of the list or string at symbol index, and store it in a variable (symbol id)
STORE_LEN = 0x6d,
STORE_LEN = 0x6e,

// @args symbol id, absolute address to jump to
// @role Compute the length of a symbol (list or string), and pop TS to compare it, then jump if false
LT_LEN_SYM_JUMP_IF_FALSE = 0x6e,
LT_LEN_SYM_JUMP_IF_FALSE = 0x6f,

// @args symbol id, offset number
// @role Multiply the symbol by (offset symbol - 2048), then push it to the stack
MUL_BY = 0x6f,
MUL_BY = 0x70,

// @args symbol index, offset number
// @role Multiply the symbol by (offset symbol - 2048), then push it to the stack
MUL_BY_INDEX = 0x70,
MUL_BY_INDEX = 0x71,

// @args symbol id, offset number
// @role Multiply the symbol by (offset symbol - 2048), then store the result using the given symbol id
MUL_SET_VAL = 0x71,
MUL_SET_VAL = 0x72,

// @args op1, op2, op3
// @role Pop 3 or 4 values from the stack, and apply the ops sequentially (only ADD, SUB, MUL, and DIV are supported). Push the result to the stack. Only op3 may be NOP.
FUSED_MATH = 0x72,
FUSED_MATH = 0x73,

InstructionsCount
};
Expand Down Expand Up @@ -481,6 +485,7 @@ namespace Ark::internal
"DEL",
"MAKE_CLOSURE",
"GET_FIELD",
"GET_FIELD_AS_CLOSURE",
"PLUGIN",
"LIST",
"APPEND",
Expand All @@ -497,7 +502,6 @@ namespace Ark::internal
"CREATE_SCOPE",
"RESET_SCOPE_JUMP",
"POP_SCOPE",
"GET_CURRENT_PAGE_ADDR",
"APPLY",
// operators
"BREAKPOINT",
Expand Down Expand Up @@ -560,6 +564,7 @@ namespace Ark::internal
"NEQ_CONST_JUMP_IF_TRUE",
"NEQ_SYM_JUMP_IF_FALSE",
"CALL_SYMBOL",
"CALL_SYMBOL_BY_INDEX",
"CALL_CURRENT_PAGE",
"GET_FIELD_FROM_SYMBOL",
"GET_FIELD_FROM_SYMBOL_INDEX",
Expand Down
2 changes: 2 additions & 0 deletions include/Ark/Compiler/IntermediateRepresentation/Entity.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ namespace Ark::internal::IR

Entity(Instruction inst, uint8_t inst2, uint8_t inst3, uint8_t inst4);

void replaceInstruction(Instruction replacement);

static Entity Label(label_t value);

static Entity Goto(const Entity& label, Instruction inst = Instruction::JUMP);
Expand Down
12 changes: 8 additions & 4 deletions include/Ark/VM/VM.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ namespace Ark

inline void jump(uint16_t address, internal::ExecutionContext& context);

Value getField(Value* closure, uint16_t id, const internal::ExecutionContext& context);
Value getField(Value* closure, uint16_t id, const internal::ExecutionContext& context, bool push_with_env = false);

Value createList(std::size_t count, internal::ExecutionContext& context);

Expand All @@ -275,13 +275,16 @@ namespace Ark
*/
inline Value* pop(internal::ExecutionContext& context);

inline Value* peek(internal::ExecutionContext& context, std::size_t offset = 0);

/**
* @brief Return a pointer to the top of the stack without consuming it, and resolve it if possible
*
* @param context
* @param offset
* @return Value*
*/
inline Value* peekAndResolveAsPtr(internal::ExecutionContext& context);
inline Value* peekAndResolveAsPtr(internal::ExecutionContext& context, std::size_t offset = 0);

/**
* @brief Push a value on the stack
Expand Down Expand Up @@ -361,7 +364,7 @@ namespace Ark
*/
uint16_t findNearestVariableIdWithValue(const Value& value, internal::ExecutionContext& context) const noexcept;

[[noreturn]] void throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext& context);
[[noreturn]] void throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext& context, bool skip_function = true);

void initDebugger(internal::ExecutionContext& context);

Expand Down Expand Up @@ -404,8 +407,9 @@ namespace Ark
* @param builtin the builtin to call
* @param argc number of arguments already sent
* @param remove_return_address remove the return address pushed by the compiler
* @param remove_builtin remove the builtin that was pushed to the stack for the call
*/
inline void callBuiltin(internal::ExecutionContext& context, const Value& builtin, uint16_t argc, bool remove_return_address = true);
inline void callBuiltin(internal::ExecutionContext& context, const Value& builtin, uint16_t argc, bool remove_return_address = true, bool remove_builtin = true);
};

#include "VM.inl"
Expand Down
Loading
Loading