Skip to content

Commit

Permalink
Switch statement code generation
Browse files Browse the repository at this point in the history
Code generation is done for switch statements, including multiple
conditions in a case, and ranges in a case.

I also changed the default semantics around print() to wrap strings to
the terminal width by default, but to truncate grids to the width.  As
part of that, I did fix format() to not write nulls in a string; we
were ignoring them, but when debugging trying to look at the raw c
string wasn't always fun.

Some more test cases added; as part of doing them, I realized I had
forgotten to do unsigned comparison operators, so fixed that as well.
  • Loading branch information
viega committed Jul 6, 2024
1 parent 325597b commit ea025fa
Show file tree
Hide file tree
Showing 20 changed files with 386 additions and 149 deletions.
7 changes: 6 additions & 1 deletion dev
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,19 @@ function libcon4m_run_tests {
}

function libcon4m_dev_clean {
for item in build debug release cicd .meson_last; do
for item in build debug release cicd; do
if [[ -d ${item} ]]; then
log Cleaning ${item}
cd ${item}
meson compile --clean
cd ..
fi
done

if [[ -f .meson_last ]]; then
rm .meson_last
fi

log "Done!"
}

Expand Down
2 changes: 1 addition & 1 deletion include/compiler/datatypes/nodeinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ typedef struct {
} c4m_control_info_t;

typedef struct {
c4m_control_info_t branch_info;
// switch() and typeof() can be labeled, but do not have automatic
// variables, so they don't ever get renamed. That's why `label`
// lives inside of branch_info, but the rest of this is in the
Expand All @@ -39,7 +40,6 @@ typedef struct {
c4m_symbol_t *lvar_2;
c4m_symbol_t *shadowed_lvar_1;
c4m_symbol_t *shadowed_lvar_2;
c4m_control_info_t branch_info;
bool ranged;
unsigned int gen_ix : 1;
unsigned int gen_named_ix : 1;
Expand Down
68 changes: 36 additions & 32 deletions include/con4m/datatypes/vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,20 +212,24 @@ typedef enum : uint8_t {
// Unused; will redo when adding objects.
C4M_ZSObjNew = 0x38,
// Box a literal, which requires supplying the type for the object.
C4M_ZBox = 0x40,
C4M_ZBox = 0x3e,
// Unbox a value literal into its actual value.
C4M_ZUnbox = 0x41,
C4M_ZUnbox = 0x3f,
// Compare (and pop) two types to see if they're comptable.
C4M_ZTypeCmp = 0x42,
C4M_ZTypeCmp = 0x40,
// Compare (and pop) two values to see if they're equal. Note that
// this is not the same as checking for object equality; this assumes
// primitive type or reference.
C4M_ZCmp = 0x43,
C4M_ZLt = 0x44,
C4M_ZLte = 0x45,
C4M_ZCmp = 0x41,
C4M_ZLt = 0x42,
C4M_ZULt = 0x43,
C4M_ZLte = 0x44,
C4M_ZULte = 0x45,
C4M_ZGt = 0x46,
C4M_ZGte = 0x47,
C4M_ZNeq = 0x48,
C4M_ZUGt = 0x47,
C4M_ZGte = 0x48,
C4M_ZUGte = 0x49,
C4M_ZNeq = 0x4A,
// Do a GTE comparison without popping.
C4M_ZGteNoPop = 0x4D,
// Do an equality comparison without popping.
Expand Down Expand Up @@ -259,23 +263,42 @@ typedef enum : uint8_t {
// Exits the current call frame, returning the current state back to the
// originating location, which is the instruction immediately following the
// C4M_Z0Call instruction that created this frame.
C4M_ZRet = 0x80,
C4M_ZRet = 0x60,
// Exits the current stack frame, returning the current state back to the
// originating location, which is the instruction immediately following the
// C4M_ZCallModule instruction that created this frame.
C4M_ZModuleRet = 0x81,
C4M_ZModuleRet = 0x61,
// Halt the current program immediately.
C4M_ZHalt = 0x82,
C4M_ZHalt = 0x62,
// Initialze module parameters. The number of parameters is encoded in the
// instruction's arg field. This is only used during module initialization.
C4M_ZModuleEnter = 0x83,
C4M_ZModuleEnter = 0x63,
// Adjust the stack pointer down by the amount encoded in the instruction's
// arg field. This means specifically that the arg field is subtracted from
// sp, so a single pop would encode -1 as the adjustment.
C4M_ZMoveSp = 0x85,
C4M_ZMoveSp = 0x65,
// Test the top stack value. If it is non-zero, pop it and continue running
// the program. Otherwise, print an assertion failure and stop running the
// program.
// Math operations have signed and unsigned variants. We can go from
// signed to unsigned where it makes sense by adding 0x10.
// Currently, we do not do this for bit ops, they are just all unsigned.
C4M_ZAdd = 0x70,
C4M_ZSub = 0x71,
C4M_ZMul = 0x72,
C4M_ZDiv = 0x73,
C4M_ZMod = 0x74,
C4M_ZBXOr = 0x75,
C4M_ZShl = 0x76,
C4M_ZShr = 0x77,
C4M_ZBOr = 0x78,
C4M_ZBAnd = 0x79,
C4M_ZBNot = 0x7A,
C4M_ZUAdd = 0x80, // Same as ZAdd
C4M_ZUSub = 0x81, // Same as ZSub
C4M_ZUMul = 0x82,
C4M_ZUDiv = 0x83,
C4M_ZUMod = 0x84,
C4M_ZAssert = 0xA0,
// Set the specified attribute to be "lock on write". Triggers an error if
// the attribute is already set to lock on write. This instruction expects
Expand All @@ -292,25 +315,6 @@ typedef enum : uint8_t {
// pop, the left operand will get replaced without additional
// movement.
//
// Math operations have signed and unsigned variants. We can go from
// signed to unsigned where it makes sense by adding 0x10.
// Currently, we do not do this for bit ops, they are just all unsigned.
C4M_ZAdd = 0xC0,
C4M_ZSub = 0xC1,
C4M_ZMul = 0xC2,
C4M_ZDiv = 0xC3,
C4M_ZMod = 0xC4,
C4M_ZBXOr = 0xC5,
C4M_ZShl = 0xC6,
C4M_ZShr = 0xC7,
C4M_ZBOr = 0xC8,
C4M_ZBAnd = 0xC9,
C4M_ZBNot = 0xCA,
C4M_ZUAdd = 0xD0,
C4M_ZUSub = 0xD1,
C4M_ZUMul = 0xD2,
C4M_ZUDiv = 0xD3,
C4M_ZUMod = 0xD4,
// Perform a logical not operation on the top stack value. If the value is
// zero, it will be replaced with a one value of the same type. If the value
// is non-zero, it will be replaced with a zero value of the same type.
Expand Down
1 change: 1 addition & 0 deletions include/con4m/string.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extern bool c4m_str_starts_with(const c4m_str_t *,
const c4m_str_t *);
extern bool c4m_str_ends_with(const c4m_str_t *,
const c4m_str_t *);
extern c4m_list_t *c4m_str_wrap(const c4m_str_t *, int64_t, int64_t);

#define c4m_str_split(x, y) c4m_str_xsplit(x, y)
// This is in richlit.c
Expand Down
3 changes: 3 additions & 0 deletions src/con4m/compiler/cfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,9 @@ cfg_process_node(cfg_ctx *ctx, c4m_cfg_node_t *node, c4m_cfg_node_t *parent)
void **v = hatrack_dict_keys_sort(node->liveness_info,
&n);

if (!ta) {
return NULL;
}
if (!ta->sometimes_live) {
ta->sometimes_live = c4m_new(c4m_type_list(c4m_type_ref()));
}
Expand Down
14 changes: 9 additions & 5 deletions src/con4m/compiler/check_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -931,8 +931,9 @@ handle_break(pass2_ctx *ctx)
c4m_control_info_t *bi = &li->branch_info;

if (!label || (bi->label && !strcmp(label->data, bi->label->data))) {
c4m_pnode_t *npnode = c4m_get_pnode(n);
c4m_jump_info_t *ji = npnode->extra_info;
c4m_pnode_t *npnode = c4m_get_pnode(n);
c4m_jump_info_t *ji = npnode->extra_info;
assert(bi);
ji->linked_control_structure = bi;
return;
}
Expand Down Expand Up @@ -1750,9 +1751,12 @@ handle_switch_statement(pass2_ctx *ctx)
c4m_pnode_t *pnode = c4m_get_pnode(saved);
c4m_control_info_t *bi = control_init(pnode->extra_info);
c4m_list_t *branches = use_pattern(ctx, c4m_case_branches);
c4m_tree_node_t *elsenode = c4m_get_match_on_node(saved, c4m_case_else);
c4m_tree_node_t *variant_node = c4m_get_match_on_node(saved, c4m_case_cond);
c4m_tree_node_t *label = c4m_get_match_on_node(saved, c4m_opt_label);
c4m_tree_node_t *elsenode = c4m_get_match_on_node(saved,
c4m_case_else);
c4m_tree_node_t *variant_node = c4m_get_match_on_node(saved,
c4m_case_cond);
c4m_tree_node_t *label = c4m_get_match_on_node(saved,
c4m_opt_label);
int ncases = c4m_list_len(branches);
c4m_cfg_node_t *entrance;
c4m_cfg_node_t *cfgbranch;
Expand Down
157 changes: 104 additions & 53 deletions src/con4m/compiler/codegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -916,75 +916,126 @@ gen_typeof(gen_ctx *ctx)
gen_apply_waiting_patches(ctx, ci);
}

static inline void
gen_range_test(gen_ctx *ctx, c4m_type_t *type)
{
// The range is already on the stack. The top will be the high,
// the low second, and the value to test first.
//
// The test value will have been duped once, but not twice. So we:
//
// 1. Pop the upper end of the range.
// 2. Run GTE test.
// 3. If successful, dupe, push the other item, and leave the test result
// as the definitive answer for the JNZ.

emit(ctx, C4M_ZPopToR0);
if (c4m_type_is_signed(type)) {
emit(ctx, C4M_ZGte);
}
else {
emit(ctx, C4M_ZUGte);
}

GEN_JZ(emit(ctx, C4M_ZDupTop);
emit(ctx, C4M_ZPushFromR0);
emit(ctx, C4M_ZLte););
}

static inline void
gen_one_case(gen_ctx *ctx, c4m_control_info_t *switch_exit, c4m_type_t *type)
{
c4m_tree_node_t *n = ctx->cur_node;
int num_conditions = n->num_kids - 1;
c4m_jump_info_t *local_jumps = c4m_gc_array_alloc(c4m_jump_info_t,
num_conditions);
c4m_jump_info_t *case_end = c4m_gc_alloc(c4m_jump_info_t);
c4m_jump_info_t *exit_jump = c4m_gc_alloc(c4m_jump_info_t);

exit_jump->linked_control_structure = switch_exit;

for (int i = 0; i < num_conditions; i++) {
emit(ctx, C4M_ZDupTop);
gen_one_kid(ctx, i);
c4m_pnode_t *pn = c4m_get_pnode(ctx->cur_node->children[i]);
if (pn->kind == c4m_nt_range) {
gen_range_test(ctx, type);
}
else {
gen_equality_test(ctx, type);
}

gen_jnz(ctx, &local_jumps[i], true);
}

// If we've checked all the conditions, and nothing matched, we jump down
// to the next case.
gen_j(ctx, case_end);
// Now, we backpatch all the local jumps to our current location.
for (int i = 0; i < num_conditions; i++) {
gen_finish_jump(ctx, &local_jumps[i]);
}

gen_one_kid(ctx, num_conditions);
// Once the kid runs, jump to the switch exit.

gen_j(ctx, exit_jump);

// Now here's the start of the next case, if it exists, so
// backpatch the jump to the case end.
gen_finish_jump(ctx, case_end);
}

static inline void
gen_switch(gen_ctx *ctx)
{
int expr_ix = 0;
c4m_tree_node_t *n = ctx->cur_node;
c4m_pnode_t *pnode = c4m_get_pnode(n);
c4m_control_info_t *ci = pnode->extra_info;
c4m_jump_info_t *ji = c4m_gc_array_alloc(c4m_jump_info_t,
n->num_kids);
int expr_ix = 0;
c4m_tree_node_t *n = ctx->cur_node;
c4m_pnode_t *pnode = c4m_get_pnode(n);
c4m_loop_info_t *ci = pnode->extra_info;
c4m_control_info_t *switch_exit = &ci->branch_info;
c4m_type_t *expr_type;

for (int i = 0; i < n->num_kids; i++) {
ji[i].linked_control_structure = pnode->extra_info;
}
// for (int i = 0; i < n->num_kids; i++) {
// ji[i].linked_control_structure = pnode->extra_info;
// }

if (gen_label(ctx, ci->label)) {
if (gen_label(ctx, ci->branch_info.label)) {
expr_ix++;
}

// Get the value to test to the top of the stack.
gen_one_kid(ctx, expr_ix);
gen_one_kid(ctx, expr_ix++);
pnode = c4m_get_pnode(n->children[expr_ix]);
expr_type = pnode->type;

for (int i = expr_ix + 1; i < n->num_kids; i++) {
c4m_tree_node_t *kid = n->children[i];
pnode = c4m_get_pnode(kid);

ctx->cur_node = kid;

if (i + 1 == n->num_kids) {
emit(ctx, C4M_ZPop);
gen_one_node(ctx);
break;
}
int n_cases = n->num_kids - expr_ix;
c4m_tree_node_t *possible_else = n->children[n->num_kids - 1];
c4m_pnode_t *kidpn = c4m_get_pnode(possible_else);
bool have_else = kidpn->kind == c4m_nt_else;

int n_conds = kid->num_kids - 1;
if (n_conds == 1) {
emit(ctx, C4M_ZDupTop);
gen_one_kid(ctx, 0);
gen_equality_test(ctx, expr_type);
GEN_JZ(gen_one_kid(ctx, 1));
}
else {
c4m_jump_info_t *local_jumps = c4m_gc_array_alloc(c4m_jump_info_t,
n_conds);

for (int j = 0; j < n_conds; j++) {
emit(ctx, C4M_ZDupTop);
gen_one_kid(ctx, j);
gen_equality_test(ctx, expr_type);
gen_jnz(ctx, &local_jumps[j], true);
}
if (have_else) {
n_cases -= 1;
}

for (int j = 0; j < n_conds; j++) {
gen_finish_jump(ctx, &local_jumps[j]);
}
}
int ix = expr_ix;

emit(ctx, C4M_ZPop);
gen_one_kid(ctx, n_conds);
gen_j(ctx, &ji[i - (expr_ix + 1)]);
for (int i = 0; i < n_cases; i++) {
ctx->cur_node = n->children[ix++];
gen_one_case(ctx, switch_exit, expr_type);
}

if (pnode->kind != c4m_nt_else) {
if (have_else) {
// No branch matched. Pop the value we were testing against, then
// run the else block, if any.
emit(ctx, C4M_ZPop);
}

gen_apply_waiting_patches(ctx, ci);
if (have_else) {
ctx->cur_node = n;
gen_one_kid(ctx, n->num_kids - 1);
}
}
gen_apply_waiting_patches(ctx, switch_exit);
}

static inline bool
Expand Down Expand Up @@ -1421,16 +1472,16 @@ gen_int_binary_op(gen_ctx *ctx, c4m_operator_t op, bool sign)
zop = C4M_ZBXOr;
break;
case c4m_op_lt:
zop = C4M_ZLt;
zop = sign ? C4M_ZLt : C4M_ZULt;
break;
case c4m_op_lte:
zop = C4M_ZLte;
zop = sign ? C4M_ZLte : C4M_ZULte;
break;
case c4m_op_gt:
zop = C4M_ZGt;
zop = sign ? C4M_ZGt : C4M_ZUGt;
break;
case c4m_op_gte:
zop = C4M_ZGte;
zop = sign ? C4M_ZGte : C4M_ZUGte;
break;
case c4m_op_eq:
zop = C4M_ZCmp;
Expand Down
Loading

0 comments on commit ea025fa

Please sign in to comment.