• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Recached

ArkScript-lang / Ark / 16238190823

12 Jul 2025 12:51PM UTC coverage: 86.717% (+0.04%) from 86.675%
16238190823

push

github

SuperFola
feat(compiler, vm): new GET_CURRENT_PAGE_ADDRESS instruction and CALL_CURRENT_PAGE super instruction to avoid a local variable lookup when performing a recursive non-tail call

97 of 101 new or added lines in 6 files covered. (96.04%)

3 existing lines in 2 files now uncovered.

7338 of 8462 relevant lines covered (86.72%)

116314.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.64
/include/Ark/VM/VM.inl
1
#ifndef ARK_VM_VM_INL
2
#define ARK_VM_VM_INL
3

4
template <typename... Args>
5
Value VM::call(const std::string& name, Args&&... args)
2✔
6
{
2✔
7
    internal::ExecutionContext& context = *m_execution_contexts.back();
2✔
8

9
    using namespace internal;
10

11
    // reset ip and pp
12
    context.ip = 0;
2✔
13
    context.pp = 0;
2✔
14

15
    // find id of function
16
    const auto it = std::ranges::find(m_state.m_symbols, name);
2✔
17
    assert(it != m_state.m_symbols.end() && "Unbound variable");
2✔
18

19
    // we need to push pp then ip manually, because the PUSH_RETURN_ADDRESS instruction is not present
20
    push(Value(static_cast<internal::PageAddr_t>(0)), context);
2✔
21
    push(Value(ValueType::InstPtr, static_cast<internal::PageAddr_t>(0)), context);
2✔
22

23
    // convert and push arguments
24
    if (sizeof...(args) > 0)
1✔
25
    {
26
        std::vector<Value> fnargs { { Value(std::forward<Args>(args))... } };
1✔
27
        for (auto&& arg : fnargs | std::views::reverse)
3✔
28
            push(arg, context);
2✔
29
    }
1✔
30

31
    // find function object and push it if it's a pageaddr/closure
32
    if (const auto dist = std::distance(m_state.m_symbols.begin(), it); std::cmp_less(dist, std::numeric_limits<uint16_t>::max()))
4✔
33
    {
34
        const uint16_t id = static_cast<uint16_t>(dist);
2✔
35
        Value* var = findNearestVariable(id, context);
2✔
36
        assert(var != nullptr && "Couldn't find variable");
2✔
37

38
        if (!var->isFunction())
2✔
39
            throwVMError(ErrorKind::Type, fmt::format("Can't call '{}': it isn't a Function but a {}", name, types_to_str[static_cast<std::size_t>(var->valueType())]));
×
40

41
        push(Value(var), context);
2✔
42
        context.last_symbol = id;
2✔
43
    }
2✔
44

45
    const std::size_t frames_count = context.fc;
2✔
46
    // call it
47
    call(context, static_cast<int16_t>(sizeof...(Args)));
2✔
48
    // reset instruction pointer, otherwise the safeRun method will start at ip = -1
49
    // without doing context.ip++ as intended (done right after the call() in the loop, but here
50
    // we start outside this loop)
51
    context.ip = 0;
2✔
52

53
    // run until the function returns
54
    safeRun(context, /* untilFrameCount */ frames_count);
2✔
55

56
    // get result
57
    return *popAndResolveAsPtr(context);
2✔
58
}
2✔
59

60
inline Value VM::resolve(internal::ExecutionContext* context, const std::vector<Value>& n)
6✔
61
{
6✔
62
    if (!n[0].isFunction())
6✔
63
        throw TypeError(fmt::format("VM::resolve couldn't resolve a non-function ({})", types_to_str[static_cast<std::size_t>(n[0].valueType())]));
×
64

65
    const std::size_t ip = context->ip;
6✔
66
    const std::size_t pp = context->pp;
6✔
67

68
    // we need to push pp then ip manually, because the PUSH_RETURN_ADDRESS instruction is not present
69
    push(Value(static_cast<internal::PageAddr_t>(pp)), *context);
6✔
70
    push(Value(ValueType::InstPtr, static_cast<internal::PageAddr_t>(ip)), *context);
6✔
71

72
    // convert and push arguments
73
    for (auto&& val : std::ranges::drop_view(n, 1) | std::views::reverse)
24✔
74
        push(val, *context);
18✔
75
    push(n[0], *context);
6✔
76

77
    const std::size_t frames_count = context->fc;
6✔
78
    // call it
79
    call(*context, static_cast<uint16_t>(n.size() - 1));
6✔
80
    // reset instruction pointer, otherwise the safeRun method will start at ip = -1
81
    // without doing context.ip++ as intended (done right after the call() in the loop, but here
82
    // we start outside this loop)
83
    context->ip = 0;
6✔
84

85
    // run until the function returns
86
    safeRun(*context, /* untilFrameCount */ frames_count);
6✔
87

88
    // restore VM state
89
    context->ip = ip;
6✔
90
    context->pp = pp;
6✔
91

92
    // get result
93
    return *popAndResolveAsPtr(*context);
6✔
94
}
6✔
95

96
#pragma region "instruction helpers"
97

98
inline Value* VM::loadSymbol(const uint16_t id, internal::ExecutionContext& context)
242,572✔
99
{
242,572✔
100
    context.last_symbol = id;
242,572✔
101
    if (Value* var = findNearestVariable(context.last_symbol, context); var != nullptr) [[likely]]
485,144✔
102
    {
103
        // push internal reference, shouldn't break anything so far, unless it's already a ref
104
        if (var->valueType() == ValueType::Reference)
242,571✔
105
            return var->reference();
9,392✔
106
        return var;
233,179✔
107
    }
108
    else [[unlikely]]
109
        throwVMError(internal::ErrorKind::Scope, fmt::format("Unbound variable `{}'", m_state.m_symbols[context.last_symbol]));
1✔
110
    return nullptr;
111
}
242,572✔
112

113
inline Value* VM::loadSymbolFromIndex(const uint16_t index, internal::ExecutionContext& context)
704,733✔
114
{
704,733✔
115
    // we need to load symbols from the end, because function calls add a reference to the current function
116
    // upon calling it. Which changes the index by 1, making it less clear, because it needs special
117
    // treatment only for function calls.
118
    auto& [id, value] = context.locals.back().atPosReverse(index);
704,733✔
119
    context.last_symbol = id;
704,733✔
120
    return &value;
704,733✔
121
}
704,733✔
122

123
inline Value* VM::loadConstAsPtr(const uint16_t id) const
332,284✔
124
{
332,284✔
125
    return &m_state.m_constants[id];
332,284✔
126
}
127

128
inline void VM::store(const uint16_t id, const Value* val, internal::ExecutionContext& context)
410,710✔
129
{
410,710✔
130
    // avoid adding the pair (id, _) multiple times, with different values
131
    Value* local = context.locals.back()[id];
410,710✔
132
    if (local == nullptr) [[likely]]
410,710✔
133
        context.locals.back().push_back(id, *val);
236,485✔
134
    else
135
        *local = *val;
174,225✔
136
}
410,710✔
137

138
inline void VM::setVal(const uint16_t id, const Value* val, internal::ExecutionContext& context)
54,381✔
139
{
54,381✔
140
    if (Value* var = findNearestVariable(id, context); var != nullptr) [[likely]]
108,762✔
141
    {
142
        if (var->valueType() == ValueType::Reference)
54,380✔
143
            *var->reference() = *val;
3,113✔
144
        else [[likely]]
145
            *var = *val;
51,267✔
146
    }
54,380✔
147
    else
148
        throwVMError(
1✔
149
            internal::ErrorKind::Scope,
150
            fmt::format(
2✔
151
                "Unbound variable `{}', can not change its value to {}",
1✔
152
                m_state.m_symbols[id],
1✔
153
                val->toString(*this)));
1✔
154
}
54,381✔
155

156
#pragma endregion
157

158
#pragma region "stack management"
159

160
inline Value* VM::pop(internal::ExecutionContext& context)
1,451,573✔
161
{
1,451,573✔
162
    if (context.sp > 0) [[likely]]
1,451,573✔
163
    {
164
        --context.sp;
1,451,573✔
165
        return &context.stack[context.sp];
1,451,573✔
166
    }
167
    return &m_undefined_value;
×
168
}
1,451,573✔
169

170
inline Value* VM::peekAndResolveAsPtr(internal::ExecutionContext& context)
24,201✔
171
{
24,201✔
172
    if (context.sp > 0)
24,201✔
173
    {
174
        Value* tmp = &context.stack[context.sp - 1];
24,201✔
175
        if (tmp->valueType() == ValueType::Reference)
24,201✔
176
            return tmp->reference();
14,891✔
177
        return tmp;
9,310✔
178
    }
24,201✔
179
    return &m_undefined_value;
×
180
}
24,201✔
181

182
inline void VM::push(const Value& value, internal::ExecutionContext& context)
70,563✔
183
{
70,563✔
184
    context.stack[context.sp] = value;
70,563✔
185
    ++context.sp;
70,563✔
186
}
70,563✔
187

188
inline void VM::push(Value&& value, internal::ExecutionContext& context)
788,187✔
189
{
788,187✔
190
    context.stack[context.sp] = std::move(value);
788,187✔
191
    ++context.sp;
788,187✔
192
}
788,187✔
193

194
inline void VM::push(Value* valptr, internal::ExecutionContext& context)
607,144✔
195
{
607,144✔
196
    context.stack[context.sp].m_type = ValueType::Reference;
607,144✔
197
    context.stack[context.sp].m_value = valptr;
607,144✔
198
    ++context.sp;
607,144✔
199
}
607,144✔
200

201
inline Value* VM::popAndResolveAsPtr(internal::ExecutionContext& context)
1,287,799✔
202
{
1,287,799✔
203
    Value* tmp = pop(context);
1,287,799✔
204
    if (tmp->valueType() == ValueType::Reference)
1,287,799✔
205
        return tmp->reference();
583,084✔
206
    return tmp;
704,715✔
207
}
1,287,799✔
208

209
#pragma endregion
210

211
inline Value* VM::findNearestVariable(const uint16_t id, internal::ExecutionContext& context) noexcept
300,071✔
212
{
300,071✔
213
    for (auto it = context.locals.rbegin(), it_end = context.locals.rend(); it != it_end; ++it)
986,688✔
214
    {
215
        if (const auto val = (*it)[id]; val != nullptr)
686,598✔
216
            return val;
300,066✔
217
    }
386,585✔
218
    return nullptr;
3✔
219
}
300,035✔
220

221
inline void VM::returnFromFuncCall(internal::ExecutionContext& context)
138,092✔
222
{
138,092✔
223
    --context.fc;
138,092✔
224
    context.stacked_closure_scopes.pop_back();
138,092✔
225
    context.locals.pop_back();
138,092✔
226
}
138,092✔
227

228
inline void VM::call(internal::ExecutionContext& context, const uint16_t argc, Value* function_ptr, internal::PageAddr_t or_address)
138,101✔
229
{
138,101✔
230
    /*
231
        Argument: number of arguments when calling the function
232
        Job: Call function from its symbol id located on top of the stack. Take the given number of
233
                arguments from the top of stack and give them  to the function (the first argument taken
234
                from the stack will be the last one of the function). The stack of the function is now composed
235
                of its arguments, from the first to the last one
236
    */
237
    using namespace internal;
238

239
    if (std::cmp_greater_equal(context.sp + 2, VMStackSize)) [[unlikely]]
138,101✔
240
        throwVMError(
4✔
241
            ErrorKind::VM,
242
            fmt::format(
2✔
243
                "Maximum recursion depth exceeded. You could consider rewriting your function `{}' to make use of tail-call optimization.",
1✔
244
                m_state.m_symbols[context.last_symbol]));
1✔
245

246
    ValueType call_type;
138,100✔
247
    Value* maybe_value_ptr = nullptr;
138,100✔
248
    if (function_ptr == nullptr && or_address == 0)
138,100✔
249
        maybe_value_ptr = popAndResolveAsPtr(context);
2,450✔
250
    else if (or_address != 0)
135,650✔
251
        call_type = ValueType::PageAddr;
109,860✔
252
    else
253
        maybe_value_ptr = function_ptr;
25,790✔
254

255
    if (maybe_value_ptr != nullptr)
138,100✔
256
    {
257
        call_type = maybe_value_ptr->valueType();
28,240✔
258
        if (call_type == ValueType::PageAddr)
28,240✔
259
            or_address = maybe_value_ptr->pageAddr();
24,563✔
260
    }
28,240✔
261

262
    context.stacked_closure_scopes.emplace_back(nullptr);
138,100✔
263

264
    switch (call_type)
138,100✔
265
    {
6✔
266
        // is it a builtin function name?
267
        case ValueType::CProc:
268
        {
269
            callBuiltin(context, *maybe_value_ptr, argc);
6✔
270
            return;
6✔
271
        }
134,423✔
272

273
        // is it a user defined function?
274
        case ValueType::PageAddr:
275
        {
276
            const PageAddr_t new_page_pointer = or_address;
134,423✔
277

278
            // create dedicated scope
279
            context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
134,423✔
280
            // set up pointers (frame counter, page, instruction)
281
            context.fc++;
134,423✔
282
            context.pp = new_page_pointer;
134,423✔
283
            context.ip = 0;
134,423✔
284
            break;
285
        }
138,091✔
286

287
        // is it a user defined closure?
288
        case ValueType::Closure:
289
        {
290
            const Closure& c = maybe_value_ptr->closure();
3,668✔
291
            const PageAddr_t new_page_pointer = c.pageAddr();
3,668✔
292

293
            // create dedicated scope
294
            context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
3,668✔
295
            // load saved scope
296
            c.refScope().mergeRefInto(context.locals.back());
3,668✔
297
            context.stacked_closure_scopes.back() = c.scopePtr();
3,668✔
298

299
            context.fc++;
3,668✔
300
            context.pp = new_page_pointer;
3,668✔
301
            context.ip = 0;
3,668✔
302
            break;
303
        }
3,671✔
304

305
        default:
306
        {
307
            throwVMError(
3✔
308
                ErrorKind::Type,
309
                fmt::format(
6✔
310
                    "{} is not a Function but a {}",
3✔
311
                    maybe_value_ptr->toString(*this), types_to_str[static_cast<std::size_t>(call_type)]));
3✔
312
        }
313
    }
138,091✔
314

315
    // checking function arity
316
    std::size_t index = 0,
138,091✔
317
                needed_argc = 0;
138,091✔
318

319
    // every argument is a MUT declaration in the bytecode
320
    while (m_state.inst(context.pp, index) == STORE)
359,038✔
321
    {
322
        needed_argc += 1;
220,947✔
323
        index += 4;  // instructions are on 4 bytes
220,947✔
324
    }
325

326
    // no store? check for CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
327
    if (index == 0 && m_state.inst(context.pp, 0) == CALL_BUILTIN_WITHOUT_RETURN_ADDRESS)
138,091✔
328
    {
329
        const uint8_t padding = m_state.inst(context.pp, context.ip + 1);
11,047✔
330
        const uint16_t arg = static_cast<uint16_t>((m_state.inst(context.pp, context.ip + 2) << 8) +
22,094✔
331
                                                   m_state.inst(context.pp, context.ip + 3));
11,047✔
332
        needed_argc = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12);
11,047✔
333
    }
11,047✔
334

335
    if (std::cmp_not_equal(needed_argc, argc)) [[unlikely]]
138,091✔
UNCOV
336
        throwArityError(argc, needed_argc, context);
×
337
}
138,101✔
338

339
inline void VM::callBuiltin(internal::ExecutionContext& context, const Value& builtin, const uint16_t argc, const bool remove_return_address)
11,827✔
340
{
11,827✔
341
    // drop arguments from the stack
342
    std::vector<Value> args;
11,827✔
343
    args.reserve(argc);
11,827✔
344

345
    for (uint16_t j = 0; j < argc; ++j)
24,537✔
346
    {
347
        // because we pull `argc` from the CALL instruction generated by the compiler,
348
        // we are guaranteed to have `argc` values pushed on the stack ; thus we can
349
        // skip the `if (context.sp > 0)` check
350
        Value* val = &context.stack[context.sp - 1 - j];
12,710✔
351
        if (val->valueType() == ValueType::Reference)
12,710✔
352
            val = val->reference();
9,406✔
353
        args.emplace_back(*val);
12,710✔
354
    }
12,710✔
355
    // +2 to skip PP/IP that were pushed by PUSH_RETURN_ADDRESS
356
    context.sp -= argc + (remove_return_address ? 2 : 0);
11,827✔
357
    // call proc
358
    push(builtin.proc()(args, this), context);
11,827✔
359
}
11,827✔
360

361
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc