Management of global variables

Also added more details on the description of the memory map, including planned
organization for local variables.

Signed-off-by: Christophe de Dinechin <christophe@dinechin.org>
This commit is contained in:
Christophe de Dinechin 2022-10-30 23:48:28 +01:00
parent 79a763cd85
commit 5a730fb549
12 changed files with 415 additions and 17 deletions

View file

@ -104,6 +104,7 @@ CXX_SOURCES += \
src/algebraic.cc \
src/arithmetic.cc \
src/functions.cc \
src/variables.cc \
src/list.cc \
src/font.cc \
fonts/HelpFont.cc \

View file

@ -6,7 +6,7 @@ extern const unsigned char EditorFont_sparse_font_data[];
const unsigned char EditorFont_sparse_font_data[66876] =
{
0x3C, 0xB8, 0x8A, 0x04, 0x42, 0x00, 0x01, 0x00, 0x33, 0x01, 0x01, 0x00, 0x00, 0x0D, 0x01, 0x00,
0x3D, 0xB8, 0x8A, 0x04, 0x42, 0x00, 0x01, 0x00, 0x33, 0x01, 0x01, 0x00, 0x00, 0x0D, 0x01, 0x00,
0x33, 0x01, 0x01, 0x08, 0x00, 0x20, 0x5F, 0x00, 0x33, 0x01, 0x01, 0x08, 0x00, 0x02, 0x13, 0x05,
0x20, 0x09, 0xEF, 0xBD, 0xF7, 0xDE, 0x7B, 0xEF, 0xBD, 0xF7, 0xDE, 0x7B, 0xEF, 0xBD, 0xF7, 0x1E,
0x00, 0xC0, 0xFD, 0xFF, 0xFF, 0x77, 0x03, 0x13, 0x0B, 0x0E, 0x11, 0x8F, 0x7F, 0xFC, 0xE3, 0x1F,

View file

@ -6,7 +6,7 @@ extern const unsigned char HelpFont_sparse_font_data[];
const unsigned char HelpFont_sparse_font_data[14189] =
{
0x3C, 0xEA, 0x6E, 0x1A, 0x00, 0x01, 0x00, 0x13, 0x01, 0x01, 0x00, 0x00, 0x0D, 0x01, 0x00, 0x13,
0x3D, 0xEA, 0x6E, 0x1A, 0x00, 0x01, 0x00, 0x13, 0x01, 0x01, 0x00, 0x00, 0x0D, 0x01, 0x00, 0x13,
0x01, 0x01, 0x03, 0x00, 0x20, 0x5F, 0x00, 0x13, 0x01, 0x01, 0x03, 0x00, 0x01, 0x06, 0x02, 0x0D,
0x04, 0xFF, 0xFF, 0xF3, 0x03, 0x01, 0x06, 0x04, 0x05, 0x06, 0xFF, 0xFF, 0x0F, 0x01, 0x06, 0x08,
0x0D, 0x0A, 0x12, 0x12, 0x14, 0x7F, 0x7F, 0x24, 0x24, 0x24, 0xFE, 0xFE, 0x28, 0x68, 0x48, 0x00,

View file

@ -6,7 +6,7 @@ extern const unsigned char StackFont_sparse_font_data[];
const unsigned char StackFont_sparse_font_data[32783] =
{
0x3C, 0x8B, 0x80, 0x02, 0x2D, 0x00, 0x01, 0x00, 0x22, 0x01, 0x01, 0x00, 0x00, 0x0D, 0x01, 0x00,
0x3D, 0x8B, 0x80, 0x02, 0x2D, 0x00, 0x01, 0x00, 0x22, 0x01, 0x01, 0x00, 0x00, 0x0D, 0x01, 0x00,
0x22, 0x01, 0x01, 0x05, 0x00, 0x20, 0x5F, 0x00, 0x22, 0x01, 0x01, 0x05, 0x00, 0x01, 0x0C, 0x03,
0x16, 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xF8, 0xFF, 0x03, 0x02, 0x0C, 0x07, 0x09, 0x0B,
0xF7, 0xFB, 0xFD, 0x7E, 0xBF, 0xDF, 0xEF, 0x77, 0x01, 0x0C, 0x0E, 0x16, 0x10, 0xC6, 0x81, 0x63,

View file

@ -52,6 +52,7 @@ SOURCES += \
../src/algebraic.cc \
../src/arithmetic.cc \
../src/functions.cc \
../src/variables.cc \
../src/list.cc \
../fonts/EditorFont.cc \
../fonts/HelpFont.cc \

View file

@ -55,6 +55,7 @@
ID(object) // Value 0 is reserved for "not implemented"
ID(symbol)
ID(catalog)
ID(text)
ID(list)
ID(program)

View file

@ -52,7 +52,7 @@ OBJECT_HANDLER_BODY(list)
case EVAL:
// List values evaluate as self
rt.push(obj);
return 0;
return OK;
case SIZE:
return size(obj, payload);
case PARSE:

View file

@ -40,10 +40,11 @@
#include "list.h"
#include "parser.h"
#include "renderer.h"
#include "text.h"
#include "runtime.h"
#include "stack-cmds.h"
#include "symbol.h"
#include "text.h"
#include "variables.h"
#include <stdio.h>

View file

@ -223,6 +223,26 @@ void runtime::move(object_p to, object_p from, size_t size, bool scratch)
}
void runtime::move_globals(object_p to, object_p from)
// ----------------------------------------------------------------------------
// Move data in the globals area
// ----------------------------------------------------------------------------
// In that case, we need to move everything up to the scratchpad
{
object_p last = (object_p) scratchpad() + allocated();
object_p first = to < from ? to : from;
size_t moving = last - first;
move(to, from, moving);
// Adjust Globals and Temporaries
int delta = to - from;
if (Globals >= first && Globals < last) // Probably never
Globals += delta;
if (Temporaries >= first && Temporaries < last) // Probably always
Temporaries += delta;
}
size_t runtime::size(object_p obj)
// ----------------------------------------------------------------------------
// Delegate the size to the object

View file

@ -37,9 +37,7 @@
struct object; // RPL object
struct global; // RPL global variable
typedef const object *object_p;
typedef const global *global_p;
RECORDER_DECLARE(runtime);
RECORDER_DECLARE(runtime_error);
@ -54,13 +52,29 @@ struct runtime
// Layout in memory is as follows
//
// HighMem End of usable memory
// [Pointer to object in local variable N in outermost program]
// [ ... ]
// [Pointer to object in local variable 1 in outermost program]
// [Number of local variables above, may be 0]
// [Pointer to next object to evaluate in outermost program]
// [... the same as the above for inner objects being evaluated ...]
// [Number of local variables in currently evaluating program, may be 0]
// Returns Top of return stack
// [Pointer to outermost catalog in path]
// [ ... intermediate catalog pointers ...]
// [Pointer to innermost catalog in path]
// StackBottom Bottom of stack
// [User stack]
// StackTop Top of stack
// ...
// [Free, may be temporarily written prior to being put in scratch]
// +Scratch Binary scratch pad (to assemble objects like lists)
// [Scratchpad allocated area]
// Editor The text editor
// Temporaries Temporaries, allocated down
// [Text editor contents]
// Temporaries Temporaries, allocated up
// [Previously allocated temporary objects, can be garbage collected]
// Globals Global named RPL objects
// [Top-level catalog of global objects]
// LowMem Bottom of memory
//
// When allocating a temporary, we move 'Temporaries' up
@ -98,7 +112,7 @@ struct runtime
StackTop = (object_p*) StackBottom;
Editing = 0;
Temporaries = (object_p) LowMem;
Globals = (global_p) Temporaries;
Globals = Temporaries;
record(runtime, "Memory %p-%p size %u (%uK)",
LowMem, HighMem, size, size>>10);
}
@ -317,7 +331,6 @@ struct runtime
return scratch;
}
size_t allocated()
// ------------------------------------------------------------------------
// Return the size of the temporary scratchpad
@ -423,18 +436,36 @@ struct runtime
// ========================================================================
//
// Object management
//
// ========================================================================
size_t gc();
void move(object_p to, object_p from, size_t sz, bool scratch=false);
size_t gc();
// ------------------------------------------------------------------------
// Garbage collector (purge unused objects from memory to make space)
// ------------------------------------------------------------------------
void move(object_p to, object_p from, size_t sz, bool scratch=false);
// ------------------------------------------------------------------------
// Like memmove, but update pointers to objects
// ------------------------------------------------------------------------
void move_globals(object_p to, object_p from);
// ------------------------------------------------------------------------
// Move data in the globals area (move everything up to end of scratch)
// ------------------------------------------------------------------------
size_t size(object_p obj);
// ------------------------------------------------------------------------
// Query the size of an RPL object
// ------------------------------------------------------------------------
size_t size(object_p obj);
object_p skip(object_p obj)
// ------------------------------------------------------------------------
// Skip an RPL object
@ -734,7 +765,7 @@ protected:
utf8 ErrorCommand; // Source of the error if known
object_p Code; // Currently executing code
object_p LowMem; // Bottom of available memory
global_p Globals; // Global objects
object_p Globals; // Global objects
object_p Temporaries; // Temporaries (must be valid objects)
size_t Editing; // Text editor (utf8 encoded)
size_t Scratch; // Scratch pad (may be invalid objects)

243
src/variables.cc Normal file
View file

@ -0,0 +1,243 @@
// ****************************************************************************
// variables.cc DB48X project
// ****************************************************************************
//
// File Description:
//
// Implementation of variables
//
// Global variables are stored in mutable catalog objects that occupy
// a reserved area of the runtime, and can grow/shrinnk as you store
// or purge global variables
//
//
//
//
// ****************************************************************************
// (C) 2022 Christophe de Dinechin <christophe@dinechin.org>
// This software is licensed under the terms outlined in LICENSE.txt
// ****************************************************************************
// This file is part of DB48X.
//
// DB48X is free software: you can redistribute it and/or modify
// it under the terms outlined in the LICENSE.txt file
//
// DB48X is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// ****************************************************************************
#include "variables.h"
#include "parser.h"
#include "renderer.h"
RECORDER(catalog, 16, "Catalogs");
RECORDER(catalog_error, 16, "Errors from catalogs");
OBJECT_HANDLER_BODY(catalog)
// ----------------------------------------------------------------------------
// Handle commands for catalogs
// ----------------------------------------------------------------------------
{
switch(op)
{
case EVAL:
// Catalogs evaluate as self
rt.push(obj);
return OK;
case SIZE:
return size(obj, payload);
case PARSE:
return object_parser(OBJECT_PARSER_ARG(), rt);
case RENDER:
return obj->object_renderer(OBJECT_RENDERER_ARG(), rt);
case HELP:
return (intptr_t) "catalog";
default:
// Check if anyone else knows how to deal with it
return DELEGATE(list);
}
}
OBJECT_PARSER_BODY(catalog)
// ----------------------------------------------------------------------------
// Try to parse this as a catalog
// ----------------------------------------------------------------------------
// Catalog should never be parsed, but do something sensible if it happens
{
return SKIP;
}
OBJECT_RENDERER_BODY(catalog)
// ----------------------------------------------------------------------------
// Render the catalog into the given catalog buffer
// ----------------------------------------------------------------------------
{
return snprintf(r.target, r.length, "Catalog (internal)");
}
bool catalog::store(gcobj name, gcobj value)
// ----------------------------------------------------------------------------
// Store an object in the catalog
// ----------------------------------------------------------------------------
// Note that the catalog itself should never move because of GC
// That's because it normally should reside in the globals area
{
runtime &rt = runtime::RT;
object_p header = (object_p) payload();
object_p body = header;
size_t old = leb128<size_t>(body); // Old size of catalog
size_t now = old; // Updated size
size_t vs = value->size(); // Size of value
if (gcobj existing = lookup(name))
{
// Replace an existing entry
gcobj evalue = existing->skip();
size_t es = evalue->size();
if (vs > es)
{
size_t requested = vs - es;
if (rt.available(requested) < requested)
return false; // Out of memory
}
// Move memory above storage if necessary
if (vs != es)
rt.move_globals((object_p) evalue + vs, (object_p) evalue + es);
// Copy new value into storage location
memmove((byte *) evalue, (byte *) value, vs);
// Compute new size of the catalog
now += vs - es;
}
else
{
// New entry, need to make room for name and value
size_t ns = name->size();
size_t vs = value->size();
size_t requested = vs + ns;
if (rt.available(requested) < requested)
return false; // Out of memory
// Move memory above end of catalog
object_p end = body + old;
rt.move_globals(end + requested, end);
// Copy name and value at end of catalog
memmove((byte *) end, (byte *) name, ns);
memmove((byte *) end + ns, (byte *) value, vs);
// Compute new size of the catalog
now += requested;
}
// Adjust catalog size
size_t nowh = leb128size(now);
size_t oldh = leb128size(old);
if (nowh != oldh)
// Header size changed, move the catalog contents and rest of globals
rt.move_globals(header + nowh, header + oldh);
leb128(header, nowh);
return true;
}
object_p catalog::lookup(object_p ref) const
// ----------------------------------------------------------------------------
// Find if the name exists in the catalog, if so return pointer to it
// ----------------------------------------------------------------------------
{
byte_p p = payload();
size_t size = leb128<size_t>(p);
size_t rsize = ref->size();
while (size)
{
object_p name = (object_p) p;
size_t ns = name->size();
if (name == ref) // Optimization when name is from catalog
return name;
if (ns == rsize && memcmp(name, ref, rsize) == 0)
return name;
p += ns;
object_p value = (object_p) p;
size_t vs = value->size();
p += vs;
// Defensive coding against malformed catalogs
if (ns + vs > size)
{
record(catalog_error,
"Malformed catalog (ns=%u vs=%u size=%u)", ns, vs, size);
return nullptr; // Malformed catalog, quick exit
}
size -= (ns + vs);
}
return nullptr;
}
object_p catalog::recall(object_p ref) const
// ----------------------------------------------------------------------------
// If the referenced object exists in catalog, return associated value
// ----------------------------------------------------------------------------
{
if (object_p found = lookup(ref))
// The value follows the name
return found->skip();
return nullptr;
}
size_t catalog::purge(object_p ref)
// ----------------------------------------------------------------------------
// Purge a name (and associated value) from the catalog
// ----------------------------------------------------------------------------
{
if (object_p name = lookup(ref))
{
size_t ns = name->size();
object_p value = name + ns;
size_t vs = value->size();
size_t purged = ns + vs;
runtime &rt = runtime::RT;
rt.move_globals(name, name + purged);
size_t old = object::size();
if (old < purged)
{
record(catalog_error,
"Purging %u bytes in %u bytes catalog", purged, old);
purged = old;
}
// Update header
object_p header = (object_p) payload();
size_t now = old - purged;
size_t oldh = leb128size(old);
size_t nowh = leb128size(now);
if (nowh > oldh)
record(catalog_error,
"Purge increased catalog size from %u to %u", oldh, nowh);
if (nowh < oldh)
// Rare case where the catalog size itself uses less bytes
rt.move_globals(header + nowh, header + oldh);
leb128(header, nowh);
return purged;
}
// If nothing purged, return 0
return 0;
}

100
src/variables.h Normal file
View file

@ -0,0 +1,100 @@
#ifndef VARIABLES_H
#define VARIABLES_H
// ****************************************************************************
// variables.h DB48X project
// ****************************************************************************
//
// File Description:
//
// Operations on variables
//
// Global variables are stored in catalog objects
// Local variables are stored just above the stack
//
//
//
//
//
// ****************************************************************************
// (C) 2022 Christophe de Dinechin <christophe@dinechin.org>
// This software is licensed under the terms outlined in LICENSE.txt
// ****************************************************************************
// This file is part of DB48X.
//
// DB48X is free software: you can redistribute it and/or modify
// it under the terms outlined in the LICENSE.txt file
//
// DB48X is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// ****************************************************************************
//
// Payload format:
//
// A catalog is represented in memory as follows:
// - The type ID (one byte, ID_directory)
// - The total length of the directory
// - For each entry:
// * An object for the name, normally an ID_symbol
// * An object for the content
//
// This organization makes it possible to put names or values from directories
// directly on the stack.
//
// Unlike the HP48, the names can be something else than symbols.
// This is used notably
//
// Searching through a catalog is done using a linear search, but given the
// small number of objects typically expected in a calculator, this should be
// fine. Note that local variables, which are more important for the
// performance of programs.
//
// Catalogs are the only mutable RPL objects.
// They can change when objects are stored or purged.
#include "list.h"
#include "runtime.h"
struct catalog : list
// ----------------------------------------------------------------------------
// Representation of a catalog
// ----------------------------------------------------------------------------
{
catalog(id type = ID_catalog): list(nullptr, 0, type)
{}
static size_t required_memory(id i)
{
return leb128size(i) + leb128size(0);
}
bool store(gcobj name, gcobj value);
// ------------------------------------------------------------------------
// Store an object in the catalog
// ------------------------------------------------------------------------
object_p recall(object_p name) const;
// ------------------------------------------------------------------------
// Check if a name exists in the catalog, return value pointer if it does
// ------------------------------------------------------------------------
object_p lookup(object_p name) const;
// ------------------------------------------------------------------------
// Check if a name exists in the catalog, return name pointer if it does
// ------------------------------------------------------------------------
size_t purge(object_p name);
// ------------------------------------------------------------------------
// Purge an entry from the catalog, return purged size
// ------------------------------------------------------------------------
OBJECT_HANDLER(catalog);
OBJECT_PARSER(catalog);
OBJECT_RENDERER(catalog);
};
typedef const catalog *catalog_p;
#endif // VARIABLES_H