mirror of
https://github.com/janet-lang/janet
synced 2026-10-12 02:31:41 +00:00
More work on value numbering. Expanding our stubs testing individual
passes. Also introduces some routines for duplicated functions and funcdefs so that we can modify bytecode in place without worrying that other places in the interpreter hold pointers into our original bytecode.
This commit is contained in:
@@ -64,9 +64,6 @@ JanetArray *janet_array_n(const Janet *elements, int32_t n) {
|
||||
array->capacity = n;
|
||||
array->count = n;
|
||||
array->data = array_allocate(sizeof(Janet), (size_t) n);
|
||||
if (!array->data) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
safe_memcpy(array->data, elements, sizeof(Janet) * n);
|
||||
return array;
|
||||
}
|
||||
|
||||
@@ -604,9 +604,6 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int
|
||||
if (janet_indexed_view(x, &arr, &count)) {
|
||||
def->constants_length = count;
|
||||
def->constants = array_allocate(sizeof(Janet), count);
|
||||
if (NULL == def->constants) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
for (i = 0; i < count; i++) {
|
||||
Janet ct = arr[i];
|
||||
def->constants[i] = ct;
|
||||
@@ -639,9 +636,6 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int
|
||||
if (a.defs_capacity < newlen) {
|
||||
int32_t newcap = newlen;
|
||||
def->defs = janet_realloc(def->defs, newcap * sizeof(JanetFuncDef *));
|
||||
if (NULL == def->defs) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
a.defs_capacity = newcap;
|
||||
}
|
||||
def->defs[def->defs_length] = subres.funcdef;
|
||||
|
||||
@@ -41,9 +41,6 @@ static JanetBuffer *janet_buffer_init_impl(JanetBuffer *buffer, int32_t capacity
|
||||
if (capacity < 4) capacity = 4;
|
||||
janet_gcpressure(capacity);
|
||||
data = array_allocate(sizeof(uint8_t), capacity);
|
||||
if (NULL == data) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
buffer->count = 0;
|
||||
buffer->capacity = capacity;
|
||||
buffer->data = data;
|
||||
|
||||
+33
-1
@@ -301,7 +301,7 @@ void janet_bytecode_movopt(JanetFuncDef *def) {
|
||||
janetc_regalloc_touch(&ra, EE);
|
||||
break;
|
||||
|
||||
/* Read B, C */
|
||||
/* Write A, Read B, C */
|
||||
case JOP_PROPAGATE:
|
||||
/* Write A, Read B and C */
|
||||
case JOP_BAND:
|
||||
@@ -555,6 +555,38 @@ JanetFuncDef *janet_funcdef_alloc(void) {
|
||||
return def;
|
||||
}
|
||||
|
||||
/* Create a duplicate that can be freely modified as no internal state
|
||||
* has a reference to it. */
|
||||
JanetFuncDef *janet_funcdef_duplicate(JanetFuncDef *original) {
|
||||
JanetFuncDef *def = janet_gcalloc(JANET_MEMORY_FUNCDEF, sizeof(JanetFuncDef));
|
||||
*def = *original;
|
||||
def->environments = array_duplicate(original->environments, sizeof(int32_t), def->environments_length);
|
||||
def->constants = array_duplicate(original->constants, sizeof(Janet), def->constants_length);
|
||||
def->bytecode = array_duplicate(original->bytecode, sizeof(uint32_t), def->bytecode_length);
|
||||
size_t bitset_len = (def->slotcount + 31) / 32;
|
||||
def->closure_bitset = array_duplicate(original->closure_bitset, sizeof(uint32_t), bitset_len);
|
||||
def->symbolmap = array_duplicate(original->symbolmap, sizeof(JanetSymbolMap), def->symbolmap_length);
|
||||
if (original->sourcemap) {
|
||||
def->sourcemap = array_duplicate(original->sourcemap, sizeof(JanetSourceMapping), def->bytecode_length);
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
/* Duplicate a function (and optionally it's def) for modifications */
|
||||
JanetFunction *janet_func_duplicate(JanetFunction *func, int duplicate_def) {
|
||||
JanetFuncDef *def = func->def;
|
||||
if (duplicate_def) {
|
||||
def = janet_funcdef_duplicate(def);
|
||||
}
|
||||
JanetFunction *newfunc = janet_gcalloc(JANET_MEMORY_FUNCTION, sizeof(JanetFunction) + def->environments_length * sizeof(JanetFuncEnv *));
|
||||
newfunc->def = def;
|
||||
|
||||
for (int32_t i = 0; i < def->environments_length; i++) {
|
||||
newfunc->envs[i] = func->envs[i];
|
||||
}
|
||||
return newfunc;
|
||||
}
|
||||
|
||||
/* Create a simple closure from a funcdef */
|
||||
JanetFunction *janet_thunk(JanetFuncDef *def) {
|
||||
JanetFunction *func = janet_gcalloc(JANET_MEMORY_FUNCTION, sizeof(JanetFunction));
|
||||
|
||||
@@ -56,9 +56,6 @@ static JanetFiber *fiber_alloc(int32_t capacity) {
|
||||
}
|
||||
fiber->capacity = capacity;
|
||||
data = array_allocate(sizeof(Janet), capacity);
|
||||
if (NULL == data) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
janet_vm.next_collection += sizeof(Janet) * capacity;
|
||||
fiber->data = data;
|
||||
return fiber;
|
||||
@@ -103,9 +100,6 @@ static void janet_fiber_refresh_memory(JanetFiber *fiber) {
|
||||
int32_t n = fiber->capacity;
|
||||
if (n) {
|
||||
Janet *newData = array_allocate(sizeof(Janet), n);
|
||||
if (NULL == newData) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
memcpy(newData, fiber->data, n * sizeof(Janet));
|
||||
janet_free(fiber->data);
|
||||
fiber->data = newData;
|
||||
|
||||
+1
-16
@@ -862,9 +862,6 @@ static const uint8_t *unmarshal_one_env(
|
||||
janet_panic("invalid funcenv length");
|
||||
}
|
||||
env->as.values = array_allocate(sizeof(Janet), length);
|
||||
if (!env->as.values) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
env->offset = 0;
|
||||
for (int32_t i = 0; i < length; i++)
|
||||
data = unmarshal_one(st, data, env->as.values + i, flags);
|
||||
@@ -966,9 +963,6 @@ static const uint8_t *unmarshal_one_def(
|
||||
/* Unmarshal constants */
|
||||
if (constants_length) {
|
||||
def->constants = array_allocate(sizeof(Janet), constants_length);
|
||||
if (!def->constants) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
for (int32_t i = 0; i < constants_length; i++)
|
||||
data = unmarshal_one(st, data, def->constants + i, flags + 1);
|
||||
} else {
|
||||
@@ -979,9 +973,6 @@ static const uint8_t *unmarshal_one_def(
|
||||
/* Unmarshal symbol map, if needed */
|
||||
if (def->flags & JANET_FUNCDEF_FLAG_HASSYMBOLMAP) {
|
||||
def->symbolmap = array_allocate(sizeof(JanetSymbolMap), symbolmap_length);
|
||||
if (def->symbolmap == NULL) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
for (int32_t i = 0; i < symbolmap_length; i++) {
|
||||
def->symbolmap[i].birth_pc = (uint32_t) readint(st, &data);
|
||||
def->symbolmap[i].death_pc = (uint32_t) readint(st, &data);
|
||||
@@ -998,9 +989,6 @@ static const uint8_t *unmarshal_one_def(
|
||||
|
||||
/* Unmarshal bytecode */
|
||||
def->bytecode = array_allocate(sizeof(uint32_t), bytecode_length);
|
||||
if (!def->bytecode) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
data = janet_unmarshal_u32s(st, data, def->bytecode, bytecode_length);
|
||||
def->bytecode_length = bytecode_length;
|
||||
|
||||
@@ -1121,9 +1109,6 @@ static const uint8_t *unmarshal_one_fiber(
|
||||
fiber->capacity = INT32_MAX;
|
||||
}
|
||||
fiber->data = array_allocate(sizeof(Janet), fiber->capacity);
|
||||
if (!fiber->data) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
for (int32_t i = 0; i < fiber->capacity; i++) {
|
||||
fiber->data[i] = janet_wrap_nil();
|
||||
}
|
||||
@@ -1484,7 +1469,7 @@ static const uint8_t *unmarshal_one(
|
||||
/* Tuple */
|
||||
Janet *tup = janet_tuple_begin(len);
|
||||
int32_t flag = readint(st, &data);
|
||||
janet_tuple_flag(tup) |= (int32_t) (((uint32_t) flag) << 16); /* Avoid left shift of negative value */
|
||||
janet_tuple_flag(tup) |= (int32_t)(((uint32_t) flag) << 16); /* Avoid left shift of negative value */
|
||||
for (int32_t i = 0; i < len; i++) {
|
||||
data = unmarshal_one(st, data, tup + i, flags + 1);
|
||||
}
|
||||
|
||||
+566
-60
@@ -29,21 +29,11 @@
|
||||
#include "symcache.h"
|
||||
#include "util.h"
|
||||
#include "vector.h"
|
||||
#include "compile.h"
|
||||
#endif
|
||||
|
||||
/* The OVM (optimistic virtual machine) is an internal interpreter for an
|
||||
* overlapping set of the base VM bytecode (in vm.c), focusing on the fast
|
||||
* path. At any point, instructions the ovm may deoptimize and fall back to the
|
||||
* default vm.c. The focus for performance will be tight loops, numerical code,
|
||||
* and hints for JIT compilation.
|
||||
*
|
||||
* The OVM bytecode is an optimized version of the interpreter bytecode, so
|
||||
* will not have a 1-1 instruction correspondence. For deopt, we will need to
|
||||
* revert PC back to where it would be in original bytecode as well as preserve
|
||||
* slot mappings. We can make some trade-offs here to allow for better
|
||||
* optimization, where VM bytecode behavior is not perfectly preserved in the
|
||||
* OVM in the case of errors. This also let's us optimize the interpreter while
|
||||
* preserving the bytecode format.
|
||||
/* The OVM (optimistic virtual machine) is a module to help optimize and compile
|
||||
* bytecode. It is a collection tools rather than a single-purpose JIT or AOT compiler.
|
||||
*
|
||||
* Intended optimization features:
|
||||
*
|
||||
@@ -59,9 +49,34 @@
|
||||
* - Strength reduction
|
||||
* - Limited LICM
|
||||
* - Inlining
|
||||
* - Disable software breakpointing
|
||||
*/
|
||||
|
||||
/* Utility functions */
|
||||
|
||||
/* Embed bitset into array of uint32_ts */
|
||||
static uint32_t bs_mask(int32_t index) {
|
||||
return ((uint32_t)1) << (index & 0x1F);
|
||||
}
|
||||
static uint32_t bs_indx(int32_t index) {
|
||||
return index >> 5;
|
||||
}
|
||||
static uint32_t *make_bitset(size_t nbits) {
|
||||
size_t bitset_len = (nbits + 31) / 32;
|
||||
if (bitset_len == 0) return NULL;
|
||||
uint32_t *bitset = janet_malloc(bitset_len * sizeof(uint32_t));
|
||||
if (NULL == bitset) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
memset(bitset, 0, bitset_len * sizeof(uint32_t));
|
||||
return bitset;
|
||||
}
|
||||
static void clear_bitset(uint32_t *bitset, size_t nbits) {
|
||||
size_t bitset_len = (nbits + 31) / 32;
|
||||
if (bitset_len) {
|
||||
memset(bitset, 0, bitset_len * sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
/* Type-checking and static analysis */
|
||||
|
||||
/* Allow easily saving states in hashtable by packing them to a Janet string */
|
||||
@@ -122,6 +137,8 @@ typedef struct {
|
||||
/* Generate conservative static typing and flow analysis with the primitive types. Turns
|
||||
* the untyped VM bytecode into typed bytecode with annotations.
|
||||
*
|
||||
* For compiler nerds, this does lattice analysis using bitsets for primitive types.
|
||||
*
|
||||
* We produce auxiliary data for each bytecode instruction:
|
||||
*
|
||||
* 1. 16-wide bitset of possible primitive types for parameter A (or D for D instructions)
|
||||
@@ -149,7 +166,8 @@ typedef struct {
|
||||
* not emitted to the target.
|
||||
*
|
||||
* Return a newly-allocated array of JanetTypeflowInstruction that contains
|
||||
* def->bytecode_length elements. The caller must free the returned pointer with janet_free. */
|
||||
* def->bytecode_length elements. The caller must free the returned pointer with janet_free.
|
||||
*/
|
||||
|
||||
JanetTypeflowInstruction *janet_bytecode_typeflow(JanetFuncDef *def, uint16_t *ret_types) {
|
||||
JanetTable *states = janet_table(0);
|
||||
@@ -581,26 +599,25 @@ JanetTypeflowInstruction *janet_bytecode_typeflow(JanetFuncDef *def, uint16_t *r
|
||||
flow[pc].flags |= TYPEFLOW_DID_PROCEED;
|
||||
pc++;
|
||||
continue;
|
||||
case JOP_SIGNAL:
|
||||
{
|
||||
if (Ic <= 4) {
|
||||
/* terminal */
|
||||
flow[pc].flags |= TYPEFLOW_INPUT_B;
|
||||
flow[pc].b_types |= types[Ib];
|
||||
flow[pc].flags |= TYPEFLOW_DID_EXIT;
|
||||
if (Ic == 0) {
|
||||
rettype |= types[Ib];
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* non terminal */
|
||||
flow[pc].flags |= TYPEFLOW_OUTPUT_A | TYPEFLOW_INPUT_B;
|
||||
flow[pc].flags |= TYPEFLOW_MAY_SIGNAL | TYPEFLOW_DID_PROCEED;
|
||||
case JOP_SIGNAL: {
|
||||
if (Ic <= 4) {
|
||||
/* terminal */
|
||||
flow[pc].flags |= TYPEFLOW_INPUT_B;
|
||||
flow[pc].b_types |= types[Ib];
|
||||
flow[pc].a_types |= 0xFFFF; /* Maybe can do better? The yield could be anything. */
|
||||
flow[pc].flags |= TYPEFLOW_DID_EXIT;
|
||||
if (Ic == 0) {
|
||||
rettype |= types[Ib];
|
||||
}
|
||||
break;
|
||||
}
|
||||
pc++;
|
||||
continue;
|
||||
/* non terminal */
|
||||
flow[pc].flags |= TYPEFLOW_OUTPUT_A | TYPEFLOW_INPUT_B;
|
||||
flow[pc].flags |= TYPEFLOW_MAY_SIGNAL | TYPEFLOW_DID_PROCEED;
|
||||
flow[pc].b_types |= types[Ib];
|
||||
flow[pc].a_types |= 0xFFFF; /* Maybe can do better? The yield could be anything. */
|
||||
}
|
||||
pc++;
|
||||
continue;
|
||||
case JOP_PROPAGATE:
|
||||
flow[pc].flags |= TYPEFLOW_OUTPUT_A | TYPEFLOW_INPUT_B | TYPEFLOW_INPUT_C;
|
||||
flow[pc].flags |= TYPEFLOW_MAY_SIGNAL | TYPEFLOW_DID_PROCEED;
|
||||
@@ -629,7 +646,7 @@ JanetTypeflowInstruction *janet_bytecode_typeflow(JanetFuncDef *def, uint16_t *r
|
||||
aType = 0xFFFF; /* We could narrow this in some cases but probably not worth it */
|
||||
}
|
||||
if (types[Ib] == (types[Ib] & (JANET_TFLAG_NIL | JANET_TFLAG_BOOLEAN | JANET_TFLAG_FUNCTION |
|
||||
JANET_TFLAG_CFUNCTION | JANET_TFLAG_NUMBER | JANET_TFLAG_POINTER))) {
|
||||
JANET_TFLAG_CFUNCTION | JANET_TFLAG_NUMBER | JANET_TFLAG_POINTER))) {
|
||||
/* B is definitely not a container type (types[Ib] is subset of non-container types) */
|
||||
/* nil or error - non container types */
|
||||
definitely_odd = 1;
|
||||
@@ -785,6 +802,7 @@ JanetTypeflowInstruction *janet_bytecode_typeflow(JanetFuncDef *def, uint16_t *r
|
||||
}
|
||||
janet_v_free(types);
|
||||
janet_v_free(state_stack);
|
||||
/* TODO - limit fixpoint for compile time? */
|
||||
janet_eprintf("iterations: %d\n", iterations);
|
||||
*ret_types = rettype;
|
||||
return flow;
|
||||
@@ -805,20 +823,54 @@ static Janet debug_typeflow_instruction(JanetTypeflowInstruction i) {
|
||||
Janet tbuf[20];
|
||||
char *c = buf;
|
||||
Janet *t = tbuf;
|
||||
if (i.flags & TYPEFLOW_OUTPUT_A) { *c++ = 'A'; *t++ = debug_mask(i.a_types); }
|
||||
if (i.flags & TYPEFLOW_OUTPUT_B) { *c++ = 'B'; *t++ = debug_mask(i.b_types); }
|
||||
if (i.flags & TYPEFLOW_OUTPUT_C) { *c++ = 'C'; *t++ = debug_mask(i.c_types); }
|
||||
if (i.flags & TYPEFLOW_OUTPUT_D) { *c++ = 'D'; *t++ = debug_mask(i.d_types); }
|
||||
if (i.flags & TYPEFLOW_OUTPUT_E) { *c++ = 'E'; *t++ = debug_mask(i.e_types); }
|
||||
if (i.flags & TYPEFLOW_OUTPUT_STACK) { *c++ = 'S'; }
|
||||
if (i.flags & TYPEFLOW_OUTPUT_A) {
|
||||
*c++ = 'A';
|
||||
*t++ = debug_mask(i.a_types);
|
||||
}
|
||||
if (i.flags & TYPEFLOW_OUTPUT_B) {
|
||||
*c++ = 'B';
|
||||
*t++ = debug_mask(i.b_types);
|
||||
}
|
||||
if (i.flags & TYPEFLOW_OUTPUT_C) {
|
||||
*c++ = 'C';
|
||||
*t++ = debug_mask(i.c_types);
|
||||
}
|
||||
if (i.flags & TYPEFLOW_OUTPUT_D) {
|
||||
*c++ = 'D';
|
||||
*t++ = debug_mask(i.d_types);
|
||||
}
|
||||
if (i.flags & TYPEFLOW_OUTPUT_E) {
|
||||
*c++ = 'E';
|
||||
*t++ = debug_mask(i.e_types);
|
||||
}
|
||||
if (i.flags & TYPEFLOW_OUTPUT_STACK) {
|
||||
*c++ = 'S';
|
||||
}
|
||||
if (c > buf) *c++ = ':';
|
||||
char *cc = c;
|
||||
if (i.flags & TYPEFLOW_INPUT_A) { *c++ = 'a'; *t++ = debug_mask(i.a_types); }
|
||||
if (i.flags & TYPEFLOW_INPUT_B) { *c++ = 'b'; *t++ = debug_mask(i.b_types); }
|
||||
if (i.flags & TYPEFLOW_INPUT_C) { *c++ = 'c'; *t++ = debug_mask(i.c_types); }
|
||||
if (i.flags & TYPEFLOW_INPUT_D) { *c++ = 'd'; *t++ = debug_mask(i.d_types); }
|
||||
if (i.flags & TYPEFLOW_INPUT_E) { *c++ = 'e'; *t++ = debug_mask(i.e_types); }
|
||||
if (i.flags & TYPEFLOW_INPUT_STACK) { *c++ = 's'; }
|
||||
if (i.flags & TYPEFLOW_INPUT_A) {
|
||||
*c++ = 'a';
|
||||
*t++ = debug_mask(i.a_types);
|
||||
}
|
||||
if (i.flags & TYPEFLOW_INPUT_B) {
|
||||
*c++ = 'b';
|
||||
*t++ = debug_mask(i.b_types);
|
||||
}
|
||||
if (i.flags & TYPEFLOW_INPUT_C) {
|
||||
*c++ = 'c';
|
||||
*t++ = debug_mask(i.c_types);
|
||||
}
|
||||
if (i.flags & TYPEFLOW_INPUT_D) {
|
||||
*c++ = 'd';
|
||||
*t++ = debug_mask(i.d_types);
|
||||
}
|
||||
if (i.flags & TYPEFLOW_INPUT_E) {
|
||||
*c++ = 'e';
|
||||
*t++ = debug_mask(i.e_types);
|
||||
}
|
||||
if (i.flags & TYPEFLOW_INPUT_STACK) {
|
||||
*c++ = 's';
|
||||
}
|
||||
if (c > cc) *c++ = ':';
|
||||
if (i.flags & TYPEFLOW_LIVE_CODE) {
|
||||
*c++ = 'l';
|
||||
@@ -868,14 +920,10 @@ static Janet debug_typeflow_instruction(JanetTypeflowInstruction i) {
|
||||
typedef struct {
|
||||
int32_t start;
|
||||
int32_t end;
|
||||
int32_t pred[2]; /* Compute later? Simplifies a lot if we ignore this */
|
||||
int32_t pred[2];
|
||||
int32_t succ[2];
|
||||
} JanetBB;
|
||||
|
||||
/* Embed bitset into array of uint32_ts */
|
||||
static uint32_t bs_mask(int32_t index) { return ((uint32_t)1) << (index & 0x1F); }
|
||||
static uint32_t bs_indx(int32_t index) { return index >> 5; }
|
||||
|
||||
/* Find a successor given a branch target */
|
||||
static int32_t janet_find_bb_with_entry(JanetBB *bbs, int32_t n, int32_t entry_idx) {
|
||||
for (int32_t i = 0; i < n; i++) {
|
||||
@@ -932,13 +980,8 @@ JanetBB *janet_basic_blocks(JanetFuncDef *def) {
|
||||
int32_t blen = def->bytecode_length;
|
||||
|
||||
/* Mark branch target locs with bitset (leaders) Add a fake leader at the end. */
|
||||
size_t bitset_len = ((size_t) blen + 64) / 32;
|
||||
uint32_t *bitset = janet_malloc(bitset_len * sizeof(uint32_t));
|
||||
if (NULL == bitset) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
memset(bitset, 0, bitset_len * sizeof(uint32_t));
|
||||
bitset[0] = 1; /* Mark first bit */
|
||||
uint32_t *bitset = make_bitset(blen + 1);
|
||||
bitset[0] = 1; /* Mark first bit as instruction 0 is a leader */
|
||||
int did_jump = 0;
|
||||
for (int32_t i = 0; i <= blen; i++) {
|
||||
int32_t target = -1;
|
||||
@@ -1055,18 +1098,462 @@ static Janet debug_basic_blocks(JanetBB *bbs) {
|
||||
return janet_wrap_array(array);
|
||||
}
|
||||
|
||||
/* Local value numbering
|
||||
*
|
||||
* Mutate local code such that blocks only use the first
|
||||
* available instance of a value.
|
||||
*
|
||||
* Will not directly try to eliminate any dead code or unused variables, but
|
||||
* this should help make such eliminations easier.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* (ldi 0 10)
|
||||
* (ldi 1 20)
|
||||
* (add 2 0 1)
|
||||
* (ldi 3 10) # slot 3 is the same as slot 0
|
||||
* (add 4 3 2)
|
||||
*
|
||||
* Goal conversion:
|
||||
*
|
||||
* (ldi 0 10)
|
||||
* (ldi 1 20)
|
||||
* (add 2 0 1)
|
||||
* (ldi 3 10) # or (movn 3 0)
|
||||
* (add 4 1 2) # use slot 0 instead slot 3
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
int32_t slot;
|
||||
int32_t num;
|
||||
} NumberedSlot;
|
||||
|
||||
typedef struct {
|
||||
Janet value;
|
||||
int32_t num;
|
||||
} NumberedValue;
|
||||
|
||||
/* Once we are making the replacement, generate
|
||||
* a load that is more suitable for
|
||||
* setting the target slot.
|
||||
*
|
||||
* Also consider adding support for new constants by appending
|
||||
* to the constant buffer. This would allow us to fully generalize to
|
||||
* constant propagation.
|
||||
*
|
||||
* TODO - some duplication of janetc_loadconst in emit.c */
|
||||
static uint32_t lvn_bytecode_loadconst(JanetFuncDef *def, Janet k, int32_t reg) {
|
||||
switch (janet_type(k)) {
|
||||
case JANET_NIL:
|
||||
return ((uint32_t) reg << 8) | JOP_LOAD_NIL;
|
||||
case JANET_BOOLEAN:
|
||||
return ((uint32_t) reg << 8) |
|
||||
(janet_unwrap_boolean(k) ? JOP_LOAD_TRUE : JOP_LOAD_FALSE);
|
||||
case JANET_NUMBER: {
|
||||
double dval = janet_unwrap_number(k);
|
||||
if (dval < INT16_MIN || dval > INT16_MAX)
|
||||
goto do_constant;
|
||||
int32_t i = (int32_t) dval;
|
||||
if (dval != i)
|
||||
goto do_constant;
|
||||
uint32_t iu = (uint32_t)i;
|
||||
return
|
||||
((uint32_t) iu << 16) |
|
||||
((uint32_t) reg << 8) |
|
||||
JOP_LOAD_INTEGER;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
do_constant: {
|
||||
int32_t cindex = def->constants_length;
|
||||
for (int32_t i = 0; i < def->constants_length; i++) {
|
||||
if (janet_equals(def->constants[i], k)) {
|
||||
cindex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
janet_assert(cindex < def->constants_length, "adding new constants NYI");
|
||||
return
|
||||
((uint32_t) cindex << 16) |
|
||||
((uint32_t) reg << 8) |
|
||||
JOP_LOAD_CONSTANT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t lvn_bytecode_domove(uint32_t dest, uint32_t src) {
|
||||
janet_assert((dest < 256) || (src < 256), "target too large");
|
||||
if (dest < 256) {
|
||||
return JOP_MOVE_NEAR | ((dest & 0xFF) << 8) | (src << 16);
|
||||
} else {
|
||||
return JOP_MOVE_FAR | ((src & 0xFF) << 8) | (dest << 16);
|
||||
}
|
||||
}
|
||||
|
||||
/* Find needle with the lowest number */
|
||||
static int32_t find_slot_for_constant(int32_t n, const NumberedValue *constants, Janet needle) {
|
||||
int32_t best = -1;
|
||||
int32_t lowest = INT32_MAX;
|
||||
for (int32_t i = 0; i < n; i++) {
|
||||
if (janet_equals(needle, constants[i].value)) {
|
||||
if (lowest > constants[i].num) {
|
||||
best = i;
|
||||
lowest = constants[i].num;
|
||||
}
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/* Value numbering (local for now) */
|
||||
static void janet_ovm_value_numbering(JanetFuncDef *def, JanetBB bb) {
|
||||
|
||||
if (bb.start >= bb.end) return;
|
||||
|
||||
/* Initial data structures */
|
||||
/* Replacements: map slot -> slot. replacements[i] is the earliest (best) slot to use as an operand for slot i. If num=-1, don't replace.
|
||||
* Constants: map -> value. constants[i] is an optional constant known to be bound to slot i. */
|
||||
int32_t num = 0;
|
||||
NumberedSlot *replacements = janet_malloc(sizeof(NumberedSlot) * def->slotcount);
|
||||
if (replacements == NULL) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
NumberedValue *constants = janet_malloc(sizeof(NumberedValue) * def->slotcount);
|
||||
if (constants == NULL) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
for (int32_t i = 0; i < def->slotcount; i++) {
|
||||
replacements[i].slot = -1;
|
||||
replacements[i].num = -1;
|
||||
constants[i].value = janet_wrap_nil();
|
||||
constants[i].num = -1;
|
||||
}
|
||||
|
||||
for (int32_t i = bb.start; i < bb.end; i++) {
|
||||
uint32_t I = def->bytecode[i];
|
||||
uint32_t Iop = I & 0x7F;
|
||||
uint32_t Ia = (I >> 8) & 0xFF;
|
||||
uint32_t Ib = (I >> 16) & 0xFF;
|
||||
uint32_t Ic = (I >> 24) & 0xFF;
|
||||
uint32_t Id = (I >> 8);
|
||||
uint32_t Ie = (I >> 16);
|
||||
int32_t Ies = ((int32_t) I >> 16);
|
||||
switch (Iop) {
|
||||
default:
|
||||
continue;
|
||||
case JOP_PUSH:
|
||||
case JOP_PUSH_2:
|
||||
case JOP_PUSH_3: {
|
||||
int32_t a = (Iop == JOP_PUSH) ? Id : Ia;
|
||||
int32_t b = (Iop == JOP_PUSH) ? -1 : (Iop == JOP_PUSH_2) ? Ie : Ib;
|
||||
int32_t c = (Iop == JOP_PUSH_3) ? Ic : -1;
|
||||
NumberedSlot arep = replacements[a];
|
||||
if (arep.num >= 0) {
|
||||
def->bytecode[i] = def->bytecode[i] & 0xFFFF00FFU;
|
||||
def->bytecode[i] = def->bytecode[i] | (((uint32_t)arep.slot & 0xFF) << 8);
|
||||
}
|
||||
if (b >= 0) {
|
||||
NumberedSlot brep = replacements[b];
|
||||
if (brep.num >= 0) {
|
||||
def->bytecode[i] = def->bytecode[i] & 0xFF00FFFFU;
|
||||
def->bytecode[i] = def->bytecode[i] | (((uint32_t)brep.slot & 0xFF) << 16);
|
||||
}
|
||||
}
|
||||
if (c >= 0) {
|
||||
NumberedSlot crep = replacements[c];
|
||||
if (crep.num >= 0) {
|
||||
def->bytecode[i] = def->bytecode[i] & 0x00FFFFFFU;
|
||||
def->bytecode[i] = def->bytecode[i] | (((uint32_t)crep.slot) << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
case JOP_MOVE_NEAR:
|
||||
case JOP_MOVE_FAR: {
|
||||
int32_t src = (Iop == JOP_MOVE_NEAR) ? Ie : Ia;
|
||||
int32_t dest = (Iop == JOP_MOVE_NEAR) ? Ia : Ie;
|
||||
if (src == dest) {
|
||||
def->bytecode[i] = JOP_NOOP;
|
||||
continue;
|
||||
}
|
||||
if (constants[src].num >= 0) { /* Propagate constants */
|
||||
constants[dest].value = constants[src].value;
|
||||
constants[dest].num = num++;
|
||||
}
|
||||
NumberedSlot src_rep = replacements[src];
|
||||
if (src_rep.num >= 0) src = src_rep.slot;
|
||||
if (constants[src].num >= 0) {
|
||||
def->bytecode[i] = lvn_bytecode_loadconst(def, constants[src].value, dest);
|
||||
}
|
||||
if (constants[src].num >= 0) { /* Propagate constants */
|
||||
constants[dest].value = constants[src].value;
|
||||
constants[dest].num = num++;
|
||||
}
|
||||
if (src == dest) {
|
||||
def->bytecode[i] = JOP_NOOP;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
case JOP_LOAD_NIL:
|
||||
case JOP_LOAD_TRUE:
|
||||
case JOP_LOAD_FALSE:
|
||||
case JOP_LOAD_INTEGER: {
|
||||
Janet x =
|
||||
(Iop == JOP_LOAD_NIL) ? janet_wrap_nil() :
|
||||
(Iop == JOP_LOAD_TRUE) ? janet_wrap_true() :
|
||||
(Iop == JOP_LOAD_FALSE) ? janet_wrap_false() :
|
||||
janet_wrap_integer(Ies);
|
||||
uint32_t dest = (Iop == JOP_LOAD_INTEGER) ? Ia : Id;
|
||||
constants[dest].num = num++;
|
||||
constants[dest].value = x;
|
||||
replacements[dest].num = num++;
|
||||
replacements[dest].slot = find_slot_for_constant(def->slotcount, constants, x);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
janet_free(replacements);
|
||||
janet_free(constants);
|
||||
}
|
||||
|
||||
/* Traverse backwards and remove writes that are never read.
|
||||
* Algorithm:
|
||||
* 1. Keep a bitset for all slots
|
||||
* 2. For every instruction, extract touched slots.
|
||||
* if slot is written to, check bit. If bit is already 1, remove instruction. Otherwise, mark bit to 1
|
||||
* if slot is read from, mark bit to 0
|
||||
* 3. If we encounter a function call, set all bits to 0 for now. We can probably use the closure_bitset
|
||||
* for more fine-grained tracking.
|
||||
*/
|
||||
static void bb_remove_redundant_writes(JanetFuncDef *def, JanetBB bb) {
|
||||
if (bb.start >= bb.end) return; /* Degenerate block */
|
||||
uint32_t *bitset = make_bitset(def->slotcount);
|
||||
/* Quick hack - if no successors, clean up writes even more - all final writes are redundant. */
|
||||
if (bb.succ[0] == -1 && bb.succ[1] == -1) {
|
||||
memset(bitset, 0xFF, (def->slotcount + 7) / 8);
|
||||
}
|
||||
for (int32_t i = bb.end - 1; i >= bb.start; i--) {
|
||||
int32_t ins[3];
|
||||
int32_t out = -1;
|
||||
int nins = 0;
|
||||
int pin = 0; /* side effects, don't remove */
|
||||
uint32_t I = def->bytecode[i];
|
||||
uint32_t Iop = I & 0x7F;
|
||||
uint32_t Ia = (I >> 8) & 0xFF;
|
||||
uint32_t Ib = (I >> 16) & 0xFF;
|
||||
uint32_t Ic = (I >> 24) & 0xFF;
|
||||
uint32_t Id = (I >> 8);
|
||||
uint32_t Ie = (I >> 16);
|
||||
/*int32_t Ies = ((int32_t) I >> 16);*/
|
||||
/* Whenever we execute an instruction that can yield or await, clear the bitset.
|
||||
* Closures could read some slots that seem to be unused, and then control could
|
||||
* return to our function. */
|
||||
switch (Iop) {
|
||||
default:
|
||||
fprintf(stderr, "opcode = %u\n", Iop);
|
||||
janet_assert(0, "unhandled instruction");
|
||||
continue;
|
||||
case JOP_JUMP:
|
||||
case JOP_NOOP:
|
||||
case JOP_RETURN_NIL:
|
||||
continue;
|
||||
/* Write A, Read E */
|
||||
case JOP_CALL:
|
||||
/* Similar logic applies to tail calls, but they are always at the end of blocks anyway */
|
||||
out = Ia;
|
||||
pin = 1;
|
||||
nins = 1;
|
||||
ins[0] = Ie;
|
||||
clear_bitset(bitset, def->slotcount);
|
||||
break;
|
||||
/* Write A, Read B */
|
||||
case JOP_SIGNAL:
|
||||
out = Ia;
|
||||
nins = 1;
|
||||
ins[0] = Ib;
|
||||
pin = 1;
|
||||
clear_bitset(bitset, def->slotcount);
|
||||
break;
|
||||
/* Read A */
|
||||
case JOP_ERROR:
|
||||
case JOP_TYPECHECK:
|
||||
case JOP_JUMP_IF:
|
||||
case JOP_JUMP_IF_NOT:
|
||||
case JOP_JUMP_IF_NIL:
|
||||
case JOP_JUMP_IF_NOT_NIL:
|
||||
case JOP_SET_UPVALUE:
|
||||
nins = 1;
|
||||
ins[0] = Ia;
|
||||
break;
|
||||
/* Write A, Read B */
|
||||
case JOP_ADD_IMMEDIATE:
|
||||
case JOP_SUBTRACT_IMMEDIATE:
|
||||
case JOP_MULTIPLY_IMMEDIATE:
|
||||
case JOP_DIVIDE_IMMEDIATE:
|
||||
case JOP_SHIFT_LEFT_IMMEDIATE:
|
||||
case JOP_SHIFT_RIGHT_IMMEDIATE:
|
||||
case JOP_SHIFT_RIGHT_UNSIGNED_IMMEDIATE:
|
||||
case JOP_GREATER_THAN_IMMEDIATE:
|
||||
case JOP_LESS_THAN_IMMEDIATE:
|
||||
case JOP_EQUALS_IMMEDIATE:
|
||||
case JOP_NOT_EQUALS_IMMEDIATE:
|
||||
case JOP_GET_INDEX:
|
||||
out = Ia;
|
||||
nins = 1;
|
||||
ins[0] = Ib;
|
||||
break;
|
||||
/* Read D */
|
||||
case JOP_RETURN:
|
||||
case JOP_PUSH:
|
||||
case JOP_PUSH_ARRAY:
|
||||
case JOP_TAILCALL:
|
||||
nins = 1;
|
||||
ins[0] = Id;
|
||||
break;
|
||||
case JOP_PUT:
|
||||
case JOP_PUSH_3:
|
||||
nins = 3;
|
||||
ins[0] = Ia;
|
||||
ins[1] = Ib;
|
||||
ins[2] = Ic;
|
||||
break;
|
||||
/* Write D */
|
||||
case JOP_LOAD_NIL:
|
||||
case JOP_LOAD_TRUE:
|
||||
case JOP_LOAD_FALSE:
|
||||
case JOP_LOAD_SELF:
|
||||
out = Id;
|
||||
break;
|
||||
/* Write A */
|
||||
case JOP_MAKE_ARRAY:
|
||||
case JOP_MAKE_BUFFER:
|
||||
case JOP_MAKE_STRING:
|
||||
case JOP_MAKE_STRUCT:
|
||||
case JOP_MAKE_TABLE:
|
||||
case JOP_MAKE_TUPLE:
|
||||
case JOP_MAKE_BRACKET_TUPLE:
|
||||
out = Ia;
|
||||
pin = 1; /* TODO - we need an instruction (or psuedo instruction) that can clear the stack but do nothing */
|
||||
break;
|
||||
case JOP_LOAD_INTEGER:
|
||||
case JOP_LOAD_CONSTANT:
|
||||
case JOP_LOAD_UPVALUE:
|
||||
case JOP_CLOSURE:
|
||||
out = Ia;
|
||||
break;
|
||||
case JOP_MOVE_FAR:
|
||||
out = Ie;
|
||||
nins = 1;
|
||||
ins[0] = Ia;
|
||||
break;
|
||||
/* Write A, Read E */
|
||||
case JOP_MOVE_NEAR:
|
||||
case JOP_LENGTH:
|
||||
case JOP_BNOT:
|
||||
out = Ia;
|
||||
nins = 1;
|
||||
ins[0] = Ie;
|
||||
break;
|
||||
/* Read A, B */
|
||||
case JOP_PUT_INDEX:
|
||||
nins = 2;
|
||||
ins[0] = Ia;
|
||||
ins[1] = Ib;
|
||||
break;
|
||||
/* Read A, E */
|
||||
case JOP_PUSH_2:
|
||||
nins = 2;
|
||||
ins[0] = Ia;
|
||||
ins[1] = Ie;
|
||||
break;
|
||||
/* A = B op C */
|
||||
case JOP_PROPAGATE:
|
||||
case JOP_RESUME:
|
||||
out = Ia;
|
||||
nins = 2;
|
||||
ins[0] = Ib;
|
||||
ins[1] = Ic;
|
||||
clear_bitset(bitset, def->slotcount);
|
||||
pin = 1;
|
||||
break;
|
||||
/* A = B op C */
|
||||
case JOP_BAND:
|
||||
case JOP_BOR:
|
||||
case JOP_BXOR:
|
||||
case JOP_ADD:
|
||||
case JOP_SUBTRACT:
|
||||
case JOP_MULTIPLY:
|
||||
case JOP_DIVIDE:
|
||||
case JOP_DIVIDE_FLOOR:
|
||||
case JOP_MODULO:
|
||||
case JOP_REMAINDER:
|
||||
case JOP_SHIFT_LEFT:
|
||||
case JOP_SHIFT_RIGHT:
|
||||
case JOP_SHIFT_RIGHT_UNSIGNED:
|
||||
case JOP_GREATER_THAN:
|
||||
case JOP_LESS_THAN:
|
||||
case JOP_EQUALS:
|
||||
case JOP_COMPARE:
|
||||
case JOP_IN:
|
||||
case JOP_GET:
|
||||
case JOP_GREATER_THAN_EQUAL:
|
||||
case JOP_LESS_THAN_EQUAL:
|
||||
case JOP_NOT_EQUALS:
|
||||
out = Ia;
|
||||
nins = 2;
|
||||
ins[0] = Ib;
|
||||
ins[1] = Ic;
|
||||
break;
|
||||
case JOP_CANCEL:
|
||||
case JOP_NEXT:
|
||||
pin = 1;
|
||||
out = Ia;
|
||||
nins = 2;
|
||||
ins[0] = Ib;
|
||||
ins[1] = Ic;
|
||||
break;
|
||||
}
|
||||
/* Test and set output bit in bitmap. If already set, change to noop. */
|
||||
if (out != -1) {
|
||||
/*fprintf(stdout, "out = %d\n", out);*/
|
||||
uint32_t mask = bs_mask(out);
|
||||
uint32_t index = bs_indx(out);
|
||||
if (!pin && (bitset[index] & mask)) {
|
||||
/*fprintf(stdout, "clearing\n");*/
|
||||
def->bytecode[i] = JOP_NOOP;
|
||||
}
|
||||
bitset[index] |= mask;
|
||||
}
|
||||
/* Clear input slots from bitmap */
|
||||
for (int j = 0; j < nins; j++) {
|
||||
/*fprintf(stdout, "input[%d] = %d\n", j, ins[j]);*/
|
||||
uint32_t mask = bs_mask(ins[j]);
|
||||
uint32_t index = bs_indx(ins[j]);
|
||||
bitset[index] &= ~mask;
|
||||
}
|
||||
}
|
||||
janet_free(bitset);
|
||||
}
|
||||
|
||||
|
||||
/* Idea: optimization pipeline
|
||||
*
|
||||
* - basic-blocks 1
|
||||
* - coarse DCE 1
|
||||
* - stack-flow analysis
|
||||
* - local value numbering
|
||||
* - coarse DCE 2
|
||||
* - ? loop analysis
|
||||
* - remove noops (invalidate existing analysis)
|
||||
* - basic-block 2
|
||||
* - stack-flow 2
|
||||
* - typeflow 1
|
||||
* - fine dead code elimination
|
||||
* - to bytecode
|
||||
* - strength reduce
|
||||
* - jump thread
|
||||
* - remove noops
|
||||
* - to machine code
|
||||
* - ssa
|
||||
@@ -1113,11 +1600,30 @@ JANET_CORE_FN(cfun_ovm_basic_blocks,
|
||||
return debug_rep;
|
||||
}
|
||||
|
||||
JANET_CORE_FN(cfun_ovm_remove_redundant_writes,
|
||||
"(ovm/remove-redundant-writes func)",
|
||||
"Remove extra writes from a function. Modifies it inline.") {
|
||||
janet_fixarity(argc, 1);
|
||||
JanetFunction *func = janet_func_duplicate(janet_getfunction(argv, 0), 1);
|
||||
JanetBB *bbs = janet_basic_blocks(func->def);
|
||||
for (int32_t i = 0; i < janet_v_count(bbs); i++) {
|
||||
janet_ovm_value_numbering(func->def, bbs[i]);
|
||||
}
|
||||
for (int32_t i = 0; i < janet_v_count(bbs); i++) {
|
||||
bb_remove_redundant_writes(func->def, bbs[i]);
|
||||
}
|
||||
janet_v_free(bbs);
|
||||
janet_bytecode_remove_noops(func->def);
|
||||
janet_verify(func->def);
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
/* Module entry point */
|
||||
void janet_lib_ovm(JanetTable *env) {
|
||||
JanetRegExt cfuns[] = {
|
||||
JANET_CORE_REG("ovm/type-flow", cfun_ovm_typeflow),
|
||||
JANET_CORE_REG("ovm/basic-blocks", cfun_ovm_basic_blocks),
|
||||
JANET_CORE_REG("ovm/remove-redundant-writes", cfun_ovm_remove_redundant_writes),
|
||||
JANET_REG_END
|
||||
};
|
||||
janet_core_cfuns_ext(env, NULL, cfuns);
|
||||
|
||||
+3
-3
@@ -734,10 +734,10 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) {
|
||||
labeld = janet_v_count(c->buffer);
|
||||
if (labeljr < labeld) {
|
||||
check_16bit_jump(c, labeljr, labelr);
|
||||
c->buffer[labeljr] |= (uint32_t) (labelr - labeljr) << 16;
|
||||
c->buffer[labeljr] |= (uint32_t)(labelr - labeljr) << 16;
|
||||
if (!tail && labeljd < labeld) {
|
||||
check_24bit_jump(c, labeljd, labeld);
|
||||
c->buffer[labeljd] |= (uint32_t) (labeld - labeljd) << 8;
|
||||
c->buffer[labeljd] |= (uint32_t)(labeld - labeljd) << 8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -991,7 +991,7 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv)
|
||||
for (int32_t i = labelwt; i < labeld; i++) {
|
||||
if (c->buffer[i] == (0x80 | JOP_JUMP)) {
|
||||
check_24bit_jump(c, i, labeld);
|
||||
c->buffer[i] = JOP_JUMP | ((uint32_t) (labeld - i) << 8);
|
||||
c->buffer[i] = JOP_JUMP | ((uint32_t)(labeld - i) << 8);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -261,9 +261,6 @@ JanetTable *janet_table_clone(JanetTable *table) {
|
||||
newTable->deleted = table->deleted;
|
||||
newTable->proto = table->proto;
|
||||
newTable->data = array_allocate(sizeof(JanetKV), newTable->capacity);
|
||||
if (NULL == newTable->data) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
memcpy(newTable->data, table->data, (size_t) table->capacity * sizeof(JanetKV));
|
||||
return newTable;
|
||||
}
|
||||
|
||||
+18
-3
@@ -584,7 +584,7 @@ static void janet_check_pointer_align(void *p) {
|
||||
uintptr_t u;
|
||||
} un;
|
||||
un.p = p;
|
||||
janet_assert(!(un.u & (uintptr_t) ((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)),
|
||||
janet_assert(!(un.u & (uintptr_t)((1 << JANET_NANBOX_64_POINTER_SHIFT) - 1)),
|
||||
"unaligned pointer wrap - cfunction pointers and abstract types must be aligned with this nanboxing configuration.");
|
||||
#endif
|
||||
}
|
||||
@@ -1216,7 +1216,22 @@ void *(janet_realloc)(void *ptr, size_t size) {
|
||||
|
||||
/* Avoid overflow and underflow, especially on 32-bit systems */
|
||||
void *array_allocate(size_t element_size, int32_t count) {
|
||||
if (count < 0) return NULL;
|
||||
janet_assert(element_size > 0, "bad element size");
|
||||
if (count <= 0) return NULL;
|
||||
if ((size_t) count > (SIZE_MAX / element_size)) return NULL;
|
||||
return janet_malloc(element_size * count);
|
||||
void *ret = janet_malloc(element_size * count);
|
||||
if (NULL == ret) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *array_duplicate(void *arr, size_t element_size, int32_t count) {
|
||||
janet_assert(element_size > 0, "bad element size");
|
||||
if (arr == NULL) return NULL;
|
||||
void *ret = array_allocate(element_size, count);
|
||||
if (ret) {
|
||||
memcpy(ret, arr, element_size * count);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -174,6 +174,7 @@ int janet_gettime(struct timespec *spec, enum JanetTimeSource source);
|
||||
|
||||
/* Safely allocate arrays */
|
||||
void *array_allocate(size_t element_size, int32_t count);
|
||||
void *array_duplicate(void *arr, size_t element_size, int32_t count);
|
||||
|
||||
/* Use LoadLibrary on windows or dlopen on posix to load dynamic libraries
|
||||
* with native code. */
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*u
|
||||
* Copyright (c) 2026 Calvin Rose
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
@@ -164,9 +164,6 @@ void *janet_memalloc_empty(int32_t count) {
|
||||
int32_t i;
|
||||
void *mem = array_allocate(sizeof(JanetKV), count);
|
||||
janet_vm.next_collection += (size_t) count * sizeof(JanetKV);
|
||||
if (NULL == mem) {
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
JanetKV *mmem = (JanetKV *)mem;
|
||||
for (i = 0; i < count; i++) {
|
||||
JanetKV *kv = mmem + i;
|
||||
|
||||
+3
-3
@@ -416,7 +416,7 @@ typedef struct JanetChannel JanetChannel;
|
||||
|
||||
/* What to do when out of memory */
|
||||
#ifndef JANET_OUT_OF_MEMORY
|
||||
#define JANET_OUT_OF_MEMORY do { fprintf(stderr, "%s:%d - janet out of memory\n", __FILE__, __LINE__); exit(1); } while (0)
|
||||
#define JANET_OUT_OF_MEMORY do { fprintf(stderr, "%s:%d - out of memory\n", __FILE__, __LINE__); exit(1); } while (0)
|
||||
#endif
|
||||
|
||||
#ifdef JANET_BSD
|
||||
@@ -1129,7 +1129,6 @@ struct JanetFuncDef {
|
||||
JanetFuncDef **defs;
|
||||
uint32_t *bytecode;
|
||||
uint32_t *closure_bitset; /* Bit set indicating which slots can be referenced by closures. */
|
||||
uint32_t *ovm_bytecode;
|
||||
|
||||
/* Various debug information */
|
||||
JanetSourceMapping *sourcemap;
|
||||
@@ -1148,7 +1147,6 @@ struct JanetFuncDef {
|
||||
int32_t defs_length;
|
||||
int32_t symbolmap_length;
|
||||
int32_t named_args_count;
|
||||
size_t ovm_bytecode_size;
|
||||
};
|
||||
|
||||
/* A function environment */
|
||||
@@ -1956,6 +1954,8 @@ JANET_API void janet_gcpressure(size_t s);
|
||||
|
||||
/* Functions */
|
||||
JANET_API JanetFuncDef *janet_funcdef_alloc(void);
|
||||
JANET_API JanetFuncDef *janet_funcdef_duplicate(JanetFuncDef *original);
|
||||
JANET_API JanetFunction *janet_func_duplicate(JanetFunction *func, int duplicate_def);
|
||||
JANET_API JanetFunction *janet_thunk(JanetFuncDef *def);
|
||||
|
||||
/* Get a function that when called with no args, will return x. */
|
||||
|
||||
Reference in New Issue
Block a user