<NME> lily_emitter.c <BEF> #include <limits.h> #include <stdint.h> #include <string.h> #include "lily_alloc.h" #include "lily_code_iter.h" #include "lily_emitter.h" #include "lily_opcode.h" #include "lily_parser.h" extern lily_type *lily_question_type; extern lily_type *lily_scoop_type; extern lily_class *lily_self_class; extern lily_type *lily_unit_type; extern lily_type *lily_unset_type; /*** * ____ _ * / ___| ___| |_ _ _ _ __ * \___ \ / _ \ __| | | | '_ \ * ___) | __/ |_| |_| | |_) | * |____/ \___|\__|\__,_| .__/ * |_| */ static lily_proto_stack *new_proto_stack(uint16_t); static void free_proto_stack(lily_proto_stack *); static lily_storage_stack *new_storage_stack(uint16_t); static void free_storage_stack(lily_storage_stack *); static void clear_storages(lily_storage_stack *, uint16_t); lily_emit_state *lily_new_emit_state(lily_symtab *symtab, lily_raiser *raiser) { lily_emit_state *emit = lily_malloc(sizeof(*emit)); lily_block *main_block = lily_malloc(sizeof(*main_block)); emit->block = main_block; emit->closure_aux_code = NULL; emit->closure_spots = lily_new_buffer_u16(4); emit->code = lily_new_buffer_u16(32); emit->expr_num = 1; emit->expr_strings = lily_new_string_pile(); emit->function_depth = 1; emit->match_cases = lily_new_buffer_u16(4); emit->patches = lily_new_buffer_u16(4); emit->protos = new_proto_stack(4); emit->raiser = raiser; emit->scope_block = main_block; emit->storages = new_storage_stack(4); emit->symtab = symtab; emit->tm = lily_new_type_maker(); emit->transform_size = 0; emit->transform_table = NULL; emit->ts = lily_new_type_system(emit->tm); main_block->block_type = block_file; main_block->class_entry = NULL; main_block->code_start = 0; main_block->forward_class_count = 0; main_block->forward_count = 0; main_block->generic_start = 0; main_block->next = NULL; main_block->next_reg_spot = 0; main_block->prev = NULL; main_block->prev_scope_block = NULL; main_block->self = NULL; main_block->storage_count = 0; main_block->var_count = 0; /* This prevents checking for 'self' from going too far. */ main_block->flags = BLOCK_SELF_ORIGIN; return emit; } void lily_rewind_emit_state(lily_emit_state *emit) { lily_block *block_iter = emit->scope_block; lily_block *main_block = block_iter; lily_storage_stack *stack = emit->storages; uint16_t total = stack->start + block_iter->storage_count; while (block_iter) { lily_block *block_next = block_iter->prev_scope_block; if (block_next == NULL) break; block_iter = block_next; } main_block = block_iter; /* Storages above `__main__` need to be cleared, or they'll be reused without fixing their ids. */ stack->start = main_block->storage_count; clear_storages(stack, total - stack->start); stack->start = 0; /* Now the buffers and the emitter. */ emit->block->forward_class_count = 0; emit->block->forward_count = 0; emit->block = main_block; emit->function_depth = 1; emit->scope_block = main_block; lily_u16_set_pos(emit->closure_spots, 0); lily_u16_set_pos(emit->code, 0); lily_u16_set_pos(emit->match_cases, 0); lily_u16_set_pos(emit->patches, 0); } void lily_free_emit_state(lily_emit_state *emit) { lily_block *current = emit->block; while (current && current->prev) current = current->prev; while (current) { lily_block *temp = current->next; lily_free(current); current = temp; } if (emit->closure_aux_code) lily_free_buffer_u16(emit->closure_aux_code); free_proto_stack(emit->protos); free_storage_stack(emit->storages); lily_free(emit->transform_table); lily_free_buffer_u16(emit->closure_spots); lily_free_buffer_u16(emit->code); lily_free_buffer_u16(emit->match_cases); lily_free_buffer_u16(emit->patches); lily_free_string_pile(emit->expr_strings); lily_free_type_maker(emit->tm); lily_free_type_system(emit->ts); lily_free(emit); } /*** * __ __ _ _ _ * \ \ / / __(_) |_(_)_ __ __ _ * \ \ /\ / / '__| | __| | '_ \ / _` | * \ V V /| | | | |_| | | | | (_| | * \_/\_/ |_| |_|\__|_|_| |_|\__, | * |___/ */ static lily_storage *get_storage(lily_emit_state *, lily_type *); static lily_block *find_deepest_loop(lily_emit_state *); static void eval_tree(lily_emit_state *, lily_ast *, lily_type *); void lily_emit_write_class_init(lily_emit_state *emit, uint16_t line_num) { lily_storage *self = emit->scope_block->self; lily_u16_write_4(emit->code, o_instance_new, self->type->cls->id, self->reg_spot, line_num); } void lily_emit_write_shorthand_ctor(lily_emit_state *emit, lily_class *cls, lily_var *var_iter) { lily_named_sym *prop_iter = cls->members; uint16_t self_reg_spot = emit->scope_block->self->reg_spot; /* The class constructor always inserts itself as the first property. Make sure to not include that. */ while (prop_iter->item_kind == ITEM_PROPERTY) { while (strcmp(var_iter->name, "") != 0) var_iter = var_iter->next; lily_u16_write_5(emit->code, o_property_set, prop_iter->reg_spot, self_reg_spot, var_iter->reg_spot, *emit->lex_linenum); prop_iter->flags &= ~SYM_NOT_INITIALIZED; var_iter = var_iter->next; prop_iter = prop_iter->next; } } void lily_emit_write_for_header(lily_emit_state *emit, lily_var *user_loop_var, lily_var *for_start, lily_var *for_end, lily_var *for_step, uint16_t line_num) { lily_sym *target; int need_sync = user_loop_var->flags & VAR_IS_GLOBAL; if (need_sync) { lily_class *cls = emit->symtab->integer_class; /* o_for_integer expects the target register to be a local. Since it isn't, do syncing reads before and after to make sure the user's loop var is what it should be. */ target = (lily_sym *)get_storage(emit, cls->self_type); } else target = (lily_sym *)user_loop_var; lily_u16_write_6(emit->code, o_for_setup, for_start->reg_spot, for_end->reg_spot, for_step->reg_spot, target->reg_spot, line_num); if (need_sync) { lily_u16_write_4(emit->code, o_global_set, target->reg_spot, user_loop_var->reg_spot, line_num); } /* Fix the start so the continue doesn't reinitialize loop vars. */ emit->block->code_start = lily_u16_pos(emit->code); lily_u16_write_5(emit->code, o_for_integer, for_start->reg_spot, for_end->reg_spot, for_step->reg_spot, target->reg_spot); lily_u16_write_2(emit->code, 5, line_num); lily_u16_write_1(emit->patches, lily_u16_pos(emit->code) - 2); if (need_sync) { lily_u16_write_4(emit->code, o_global_set, target->reg_spot, user_loop_var->reg_spot, line_num); } } /* This is called before 'continue', 'break', or 'return' is written. It writes the appropriate number of try+catch pop instructions to offset the movement. A search is done from the current block down to 'stop_block' to find out how many try pop's to write. */ static void write_pop_try_blocks_up_to(lily_emit_state *emit, lily_block *stop_block) { lily_block *block_iter = emit->block; int try_count = 0; while (block_iter != stop_block) { if (block_iter->block_type == block_try && (block_iter->flags & BLOCK_HAS_BRANCH) == 0) try_count++; block_iter = block_iter->prev; } if (try_count) { int i; for (i = 0;i < try_count;i++) lily_u16_write_1(emit->code, o_catch_pop); } } /* The parser has a 'break' and wants the emitter to write the code. */ int lily_emit_try_write_break(lily_emit_state *emit) { lily_block *block = find_deepest_loop(emit); if (block == NULL) return 0; write_pop_try_blocks_up_to(emit, block); lily_u16_write_2(emit->code, o_jump, 1); uint16_t patch = lily_u16_pos(emit->code) - 1; if (emit->block == block) lily_u16_write_1(emit->patches, patch); else { lily_u16_inject(emit->patches, block->next->patch_start, patch); /* The blocks after the one that got the new patch need to have their starts adjusted or they'll think it belongs to them. */ for (block = block->next; block; block = block->next) block->patch_start++; } return 1; } /* The parser has a 'continue' and wants the emitter to write the code. */ int lily_emit_try_write_continue(lily_emit_state *emit) { lily_block *loop_block = find_deepest_loop(emit); if (loop_block == NULL) return 0; write_pop_try_blocks_up_to(emit, loop_block); uint16_t where = loop_block->code_start - lily_u16_pos(emit->code); lily_u16_write_2(emit->code, o_jump, (uint16_t)where); return 1; } /* Write a conditional jump. 0 means jump if false, 1 means jump if true. The ast is the thing to test. */ static void emit_jump_if(lily_emit_state *emit, lily_ast *ast, uint16_t jump_on) { lily_u16_write_4(emit->code, o_jump_if, jump_on, ast->result->reg_spot, 3); lily_u16_write_1(emit->patches, lily_u16_pos(emit->code) - 1); } /* If no patches have been written since 'start', this does nothing. Otherwise, this fixes patches written after 'start', as well as 'start' itself. They're fixed to the current code position. */ static void write_patches_since(lily_emit_state *emit, uint16_t start) { uint16_t pos = lily_u16_pos(emit->code); uint16_t stop = lily_u16_pos(emit->patches); uint16_t iter; for (iter = start;iter != stop;iter++) { uint16_t patch = lily_u16_get(emit->patches, iter); /* Skip 0's (those are patches that have been optimized out. Here's a bit of math: If the vm is at 'x' and wants to get to 'y', it can add 'y - x' to 'x', and have 'y'. Cool, right? The trouble is that jump positions may be +1 or +2 relative to the position of the opcode. This problem is worked around by having jumps write down their offset to the opcode, and including that in the jump. */ if (patch != 0) { uint16_t adjust = lily_u16_get(emit->code, patch); lily_u16_set_at(emit->code, patch, pos + adjust - patch); } } lily_u16_set_pos(emit->patches, start); } /*** * ____ _ * / ___|| |_ ___ _ __ __ _ __ _ ___ ___ * \___ \| __/ _ \| '__/ _` |/ _` |/ _ \/ __| * ___) | || (_) | | | (_| | (_| | __/\__ \ * |____/ \__\___/|_| \__,_|\__, |\___||___/ * |___/ */ static lily_storage *new_storage(void) { lily_storage *result = lily_malloc(sizeof(*result)); result->type = NULL; result->expr_num = 0; result->flags = 0; result->item_kind = ITEM_STORAGE; return result; } /** Storages are used to hold intermediate values. The emitter is responsible for handing them out, controlling their position, and making new ones. Most of that is done in get_storage. **/ static lily_storage_stack *new_storage_stack(uint16_t initial) { lily_storage_stack *result = lily_malloc(sizeof(*result)); uint16_t i; result->data = lily_malloc(initial * sizeof(*result->data)); for (i = 0;i < initial;i++) { lily_storage *s = new_storage(); result->data[i] = s; } result->start = 0; result->size = initial; return result; } static void free_storage_stack(lily_storage_stack *stack) { uint16_t i; for (i = 0;i < stack->size;i++) lily_free(stack->data[i]); lily_free(stack->data); lily_free(stack); } static void grow_storages(lily_storage_stack *stack) { uint16_t new_size = stack->size * 2; lily_storage **new_data = lily_realloc(stack->data, sizeof(*new_data) * new_size); uint16_t i; /* Storages are taken pretty often, so eagerly initialize them for a little bit more speed. */ for (i = stack->size;i < new_size;i++) new_data[i] = new_storage(); stack->data = new_data; stack->size = new_size; } /* When a callable block exits, the storages need to be cleared. Clearing the storages prevents outer functions from using storages with wrong ids, which leads to very bad results. */ static void clear_storages(lily_storage_stack *stack, uint16_t count) { uint16_t i; for (i = stack->start;i < stack->start + count;i++) stack->data[i]->type = NULL; } /* This attempts to grab a storage of the given type. It will first attempt to get a used storage, then a new one. */ static lily_storage *get_storage(lily_emit_state *emit, lily_type *type) { lily_storage_stack *stack = emit->storages; uint32_t expr_num = emit->expr_num; uint16_t i; lily_storage *s = NULL; for (i = stack->start;i < stack->size;i++) { s = stack->data[i]; /* A storage with a type of NULL is not in use and can be claimed. */ if (s->type == NULL) { s->type = type; s->flags = SYM_NOT_ASSIGNABLE; s->reg_spot = emit->scope_block->next_reg_spot; emit->scope_block->next_reg_spot++; i++; if (i == stack->size) grow_storages(emit->storages); emit->scope_block->storage_count++; break; } else if (s->type == type && s->expr_num != expr_num && (s->flags & STORAGE_IS_LOCKED) == 0) { s->expr_num = expr_num; s->flags = SYM_NOT_ASSIGNABLE; break; } } s->expr_num = expr_num; return s; } /*** * ____ _ _ * | __ )| | ___ ___| | _____ * | _ \| |/ _ \ / __| |/ / __| * | |_) | | (_) | (__| <\__ \ * |____/|_|\___/ \___|_|\_\___/ * */ static void perform_closure_transform(lily_emit_state *, lily_block *, lily_function_val *); static lily_block *next_block(lily_emit_state *emit) { lily_block *new_block; if (emit->block->next == NULL) { new_block = lily_malloc(sizeof(*new_block)); emit->block->next = new_block; new_block->prev = emit->block; new_block->next = NULL; } else new_block = emit->block->next; new_block->class_entry = emit->block->class_entry; new_block->self = NULL; new_block->patch_start = lily_u16_pos(emit->patches); /* This can't be 0, or `define f: Integer {}` passes if no code has been written before it. */ new_block->last_exit = UINT16_MAX; new_block->flags = 0; new_block->var_count = 0; new_block->code_start = lily_u16_pos(emit->code); new_block->forward_count = 0; return new_block; } static void setup_scope_block(lily_emit_state *emit, lily_block *new_block) { new_block->prev_scope_block = emit->scope_block; new_block->next_reg_spot = 0; new_block->storage_count = 0; new_block->code_start = lily_u16_pos(emit->code); emit->storages->start += emit->scope_block->storage_count; emit->scope_block = new_block; emit->block = new_block; } void lily_emit_enter_anon_block(lily_emit_state *emit) { lily_block *block = next_block(emit); block->block_type = block_anon; emit->block = block; } void lily_emit_enter_class_block(lily_emit_state *emit, lily_var *var) { lily_block *block = next_block(emit); block->flags |= BLOCK_CLOSURE_ORIGIN | BLOCK_SELF_ORIGIN; block->block_type = block_class; block->scope_var = var; block->class_entry = var->parent; setup_scope_block(emit, block); emit->function_depth++; } void lily_emit_enter_define_block(lily_emit_state *emit, lily_var *var) { lily_block *block = next_block(emit); lily_block_type scope_block_type = emit->scope_block->block_type; if (scope_block_type & (SCOPE_CLASS | SCOPE_ENUM)) block->flags |= BLOCK_CLOSURE_ORIGIN | BLOCK_SELF_ORIGIN; else if (scope_block_type == block_file) block->flags |= BLOCK_CLOSURE_ORIGIN; else if (scope_block_type == block_define) /* This var will need upvalues if it's called. */ var->flags |= VAR_NEEDS_CLOSURE; block->block_type = block_define; block->scope_var = var; setup_scope_block(emit, block); emit->function_depth++; if (var->parent && (var->flags & VAR_IS_STATIC) == 0) { lily_emit_create_block_self(emit, var->parent->self_type); lily_emit_activate_block_self(emit); } } void lily_emit_enter_do_while_block(lily_emit_state *emit) { lily_block *block = next_block(emit); block->block_type = block_do_while; emit->block = block; } void lily_emit_enter_enum_block(lily_emit_state *emit, lily_class *cls) { lily_block *block = next_block(emit); /* Enum blocks exist as scope blocks so that enum methods know they're enum methods. They don't have a var since they don't execute code. */ block->block_type = block_enum; block->class_entry = cls; setup_scope_block(emit, block); emit->function_depth++; } void lily_emit_enter_file_block(lily_emit_state *emit, lily_var *var) { lily_block *block = next_block(emit); block->forward_class_count = 0; block->block_type = block_file; block->scope_var = var; setup_scope_block(emit, block); /* Don't bump depth so these vars are seen as global vars. */ } void lily_emit_enter_foreach_block(lily_emit_state *emit) { lily_block *block = next_block(emit); block->block_type = block_foreach; emit->block = block; } void lily_emit_enter_for_in_block(lily_emit_state *emit) { lily_block *block = next_block(emit); block->block_type = block_for_in; emit->block = block; } void lily_emit_enter_if_block(lily_emit_state *emit) { lily_block *block = next_block(emit); block->flags |= BLOCK_ALWAYS_EXITS; block->block_type = block_if; emit->block = block; } void lily_emit_enter_lambda_block(lily_emit_state *emit, lily_var *var) { lily_block *block = next_block(emit); lily_block_type scope_block_type = emit->scope_block->block_type; if (scope_block_type == block_class) block->flags |= BLOCK_SELF_ORIGIN; else if (scope_block_type == block_file) block->flags |= BLOCK_CLOSURE_ORIGIN; block->block_type = block_lambda; block->scope_var = var; setup_scope_block(emit, block); emit->function_depth++; } void lily_emit_enter_match_block(lily_emit_state *emit) { lily_block *block = next_block(emit); block->flags |= BLOCK_ALWAYS_EXITS; block->block_type = block_match; block->match_case_start = lily_u16_pos(emit->match_cases); emit->block = block; } void lily_emit_enter_try_block(lily_emit_state *emit, uint16_t line_num) { lily_block *block = next_block(emit); block->flags |= BLOCK_ALWAYS_EXITS; block->block_type = block_try; emit->block = block; /* Each branch of a try block contains a jump to the next branch. */ lily_u16_write_3(emit->code, o_catch_push, 1, line_num); lily_u16_write_1(emit->patches, lily_u16_pos(emit->code) - 2); } void lily_emit_enter_while_block(lily_emit_state *emit) { lily_block *block = next_block(emit); block->block_type = block_while; emit->block = block; } void lily_emit_enter_with_block(lily_emit_state *emit) { lily_block *block = next_block(emit); block->flags |= BLOCK_ALWAYS_EXITS; block->block_type = block_with; block->match_case_start = lily_u16_pos(emit->match_cases); emit->block = block; } void lily_emit_leave_block(lily_emit_state *emit) { lily_block *block = emit->block; int block_type = block->block_type; /* These blocks need to jump back up when the bottom is hit. */ if (block_type == block_while || block_type == block_foreach || block_type == block_for_in) { int x = block->code_start - lily_u16_pos(emit->code); lily_u16_write_2(emit->code, o_jump, (uint16_t)x); } else if (block_type == block_match) lily_u16_set_pos(emit->match_cases, emit->block->match_case_start); else if (block_type == block_try) /* The vm expects that the last except block will have a 'next' of 0 to indicate the end of the 'except' chain. Remove the patch that the last except block installed so it doesn't get patched. */ lily_u16_set_at(emit->code, lily_u16_pop(emit->patches), 0); if ((block->flags & BLOCK_ALWAYS_EXITS) && (block->flags & BLOCK_FINAL_BRANCH) && block->last_exit == lily_u16_pos(emit->code)) { emit->block->prev->last_exit = lily_u16_pos(emit->code); } write_patches_since(emit, block->patch_start); emit->block = emit->block->prev; } static void finish_block_code(lily_emit_state *emit) { lily_block *block = emit->scope_block; lily_var *var = block->scope_var; lily_value *v = lily_literal_at(emit->symtab, var->reg_spot); lily_function_val *f = v->value.function; uint16_t code_start, code_size; uint16_t *source; if ((block->flags & BLOCK_MAKE_CLOSURE) == 0) { code_start = emit->block->code_start; code_size = lily_u16_pos(emit->code) - emit->block->code_start; source = emit->code->data; } else { perform_closure_transform(emit, block, f); if ((block->flags & BLOCK_CLOSURE_ORIGIN) == 0) block->prev_scope_block->flags |= BLOCK_MAKE_CLOSURE; code_start = 0; code_size = lily_u16_pos(emit->closure_aux_code); source = emit->closure_aux_code->data; } uint16_t *code = lily_malloc((code_size + 1) * sizeof(*code)); memcpy(code, source + code_start, sizeof(*code) * code_size); f->code_len = code_size; f->code = code; f->proto->code = code; f->reg_count = block->next_reg_spot; } static int try_write_define_exit(lily_emit_state *emit, uint16_t line_num) { lily_block *block = emit->block; int result = 1; if (block->last_exit != lily_u16_pos(emit->code)) { lily_type *type = block->scope_var->type->subtypes[0]; if (type == lily_unit_type) lily_u16_write_2(emit->code, o_return_unit, line_num); else if (type == lily_self_class->self_type) /* The implicit 'self' of a class method is always first (at 0). */ lily_u16_write_3(emit->code, o_return_value, 0, line_num); else result = 0; } return result; } void lily_emit_leave_class_block(lily_emit_state *emit, uint16_t line_num) { uint16_t self_reg = emit->block->self->reg_spot; lily_u16_write_3(emit->code, o_return_value, self_reg, line_num); finish_block_code(emit); lily_emit_leave_scope_block(emit); } void lily_emit_leave_define_block(lily_emit_state *emit, uint16_t line_num) { lily_block *block = emit->block; if ((block->scope_var->flags & SYM_IS_FORWARD) == 0) { if (try_write_define_exit(emit, line_num) == 0) lily_raise_syn(emit->raiser, "Missing return statement at end of function."); finish_block_code(emit); } else block->prev->forward_count++; lily_emit_leave_scope_block(emit); } void lily_emit_leave_import_block(lily_emit_state *emit, uint16_t line_num, uint16_t last_line) { lily_var *var = emit->scope_block->scope_var; lily_u16_write_2(emit->code, o_return_unit, last_line); finish_block_code(emit); /* These blocks don't bump the depth since that's used for determining globals. Bump the depth to offset the drop that this function does. */ emit->function_depth++; lily_emit_leave_scope_block(emit); lily_storage *s = get_storage(emit, lily_unit_type); lily_u16_write_5(emit->code, o_call_native, var->reg_spot, 0, s->reg_spot, line_num); } void lily_emit_leave_lambda_block(lily_emit_state *emit, uint16_t line_num) { if (emit->block->last_exit != lily_u16_pos(emit->code)) lily_u16_write_2(emit->code, o_return_unit, line_num); finish_block_code(emit); lily_emit_leave_scope_block(emit); } void lily_emit_leave_scope_block(lily_emit_state *emit) { lily_block *block = emit->block; lily_u16_set_pos(emit->code, block->code_start); clear_storages(emit->storages, block->storage_count); emit->scope_block = block->prev_scope_block; emit->storages->start -= emit->scope_block->storage_count; emit->block = emit->block->prev; emit->function_depth--; } static lily_block *find_deepest_loop(lily_emit_state *emit) { lily_block *block_iter = emit->block; lily_block *stop_block = emit->scope_block; lily_block *result = NULL; for (;block_iter != stop_block;block_iter = block_iter->prev) { if (block_iter->block_type == block_while || block_iter->block_type == block_do_while || block_iter->block_type == block_for_in || block_iter->block_type == block_foreach) { result = block_iter; break; } } return result; } /* This handles branch changes for if, match, and try. Each condition finishes by writing a jump to the next branch in case of failure. A patch of zero is used as a placeholder in case of, for example, a branch that does not have a jump because it is always taken. */ void lily_emit_branch_switch(lily_emit_state *emit) { lily_block *block = emit->block; uint16_t patch = lily_u16_pop(emit->patches); /* The spot in code has an offset for the patch. */ uint16_t adjust = lily_u16_get(emit->code, patch); if (block->last_exit != lily_u16_pos(emit->code)) { if ((block->flags & BLOCK_HAS_BRANCH) == 0 && block->block_type == block_try) lily_u16_write_1(emit->code, o_catch_pop); /* Since the current branch isn't confirmed to exit, write an exit jump. This exit jump will persist until the block is done. */ lily_u16_write_2(emit->code, o_jump, 1); lily_u16_write_1(emit->patches, lily_u16_pos(emit->code) - 1); block->flags &= ~BLOCK_ALWAYS_EXITS; } if (patch != 0) { lily_u16_set_at(emit->code, patch, lily_u16_pos(emit->code) + adjust - patch); } block->flags |= BLOCK_HAS_BRANCH; } void lily_emit_branch_finalize(lily_emit_state *emit) { lily_emit_branch_switch(emit); emit->block->flags |= BLOCK_FINAL_BRANCH; } void lily_emit_except_switch(lily_emit_state *emit, lily_class *except_cls, lily_var *except_var, uint16_t line_num) { if (except_cls->id != LILY_ID_EXCEPTION) lily_emit_branch_switch(emit); else lily_emit_branch_finalize(emit); lily_u16_write_4(emit->code, o_exception_catch, except_cls->id, 2, line_num); lily_u16_write_1(emit->patches, lily_u16_pos(emit->code) - 2); if (except_var) lily_u16_write_2(emit->code, o_exception_store, except_var->reg_spot); } void lily_emit_create_block_self(lily_emit_state *emit, lily_type *self_type) { /* It's important that the storage for self not be written over. Use a type that isn't allowed to make sure it's unique, then lock the storage. */ lily_storage *self = get_storage(emit, lily_scoop_type); self->type = self_type; self->flags |= STORAGE_IS_LOCKED; /* This isn't cleared by default because it's the only storage that can be closed over. */ self->closure_spot = UINT16_MAX; emit->scope_block->self = self; } void lily_emit_activate_block_self(lily_emit_state *emit) { lily_block *block = emit->scope_block; lily_block_type block_type = block->block_type; uint16_t flags; if (block_type == block_class) flags = SELF_KEYWORD | SELF_METHOD | SELF_PROPERTY; else { lily_class *self_cls = block->self->type->cls; if (self_cls->item_kind & ITEM_IS_CLASS) flags = SELF_KEYWORD | SELF_METHOD | SELF_PROPERTY; else flags = SELF_KEYWORD | SELF_METHOD; } block->flags |= flags; } #define SELF_FLAGS (SELF_KEYWORD | SELF_PROPERTY | SELF_METHOD) static int can_use_self(lily_emit_state *emit, uint16_t flag) { lily_block *block = emit->scope_block; /* This block has a self that supports this action. */ if (block->flags & flag) return 1; /* Is there a self that can be pulled from a closure? */ lily_block *origin = block; while ((origin->flags & BLOCK_SELF_ORIGIN) == 0) origin = origin->prev_scope_block; if ((origin->flags & flag) == 0) return 0; /* Make sure self is going to be in the closure. */ lily_storage *origin_self = origin->self; if (origin_self->closure_spot == UINT16_MAX) { /* The resulting depth for the backing closure is always the same: __main__ is 1, class is 2, backing define is 3. */ uint16_t depth = 3; lily_u16_write_2(emit->closure_spots, origin_self->reg_spot, depth); uint16_t spot = (lily_u16_pos(emit->closure_spots) - 1) / 2; origin_self->closure_spot = spot; } /* Create a self in this block with information copied over. */ lily_emit_create_block_self(emit, origin_self->type); block->flags |= BLOCK_MAKE_CLOSURE | (origin->flags & SELF_FLAGS); block->self->closure_spot = origin_self->closure_spot; return 1; } #undef SELF_FLAGS int lily_emit_can_use_self_keyword(lily_emit_state *emit) { return can_use_self(emit, SELF_KEYWORD); } int lily_emit_can_use_self_method(lily_emit_state *emit) { return can_use_self(emit, SELF_METHOD); } int lily_emit_can_use_self_property(lily_emit_state *emit) { return can_use_self(emit, SELF_PROPERTY); } /*** * ____ _ * / ___| | ___ ___ _ _ _ __ ___ ___ * | | | |/ _ \/ __| | | | '__/ _ \/ __| * | |___| | (_) \__ \ |_| | | | __/\__ \ * \____|_|\___/|___/\__,_|_| \___||___/ * */ /** Closures are a tricky part of the emitter. The values that are stored in a closure are termed its cells. A closure's cells must always be fresh. If a variable is assigned to 10 in one scope, then it must be seen as 10 at any point in any other scope. The current implementation of closures works by 'shadowing' assignments. If a value that is closed over is assigned something, then a secondary assignment is made to the closure cell. Similarly, if a value is read from and closed over, then it needs to be pulled from the closure first. This is complicated by not having exact metrics of what is and isn't closed over. A define within a define is easy to keep track of, but lambdas are not so easy to track. This implementation was chosen because Lily has a register-based vm instead of a stack-based one. There are other complications. Generics get...strange. If two functions, f and g, both take a type A, they may be called such that their A's disagree. A barrier has been setup to prevent doing wrong things. Lambdas, however, can close over untyped variables because they do not create a generic scope. Another tough issue is self. A decision (which may be reverted) is that lambdas should be able to close over self. This allows methods to be easily called within a lambda, even if the lambda may not be a class member. However, it carries a side-effect of making method access need to check for self existing either closed over or not. **/ /* This writes o_closure_function which will create a copy of 'func_sym' but with closure information. 'target' is a storage where the closed-over copy will end up. The result cannot be cached in any way (each invocation should get a fresh set of cells). */ static void emit_create_function(lily_emit_state *emit, lily_sym *func_sym, lily_storage *target) { lily_u16_write_4(emit->code, o_closure_function, func_sym->reg_spot, target->reg_spot, *emit->lex_linenum); emit->scope_block->flags |= BLOCK_MAKE_CLOSURE; } /* This function closes over a var, but has a requirement. The requirement is that it cannot be an unsolved type from a higher-up scope. This requirement exists because Lily does not currently understand how to have two different kinds of a generic that are not equivalent / scoped generics. */ static uint16_t checked_close_over_var(lily_emit_state *emit, lily_var *var) { if (emit->scope_block->block_type == block_define && emit->scope_block->prev->block_type == block_define && var->type->flags & TYPE_IS_UNRESOLVED) lily_raise_syn(emit->raiser, "Cannot close over a var of an incomplete type in this scope."); if (var->flags & VAR_CANNOT_BE_UPVALUE) lily_raise_syn(emit->raiser, "Not allowed to close over variables from a class constructor."); emit->scope_block->flags |= BLOCK_MAKE_CLOSURE; lily_u16_write_2(emit->closure_spots, var->reg_spot, var->function_depth); uint16_t spot = (lily_u16_pos(emit->closure_spots) - 1) / 2; var->closure_spot = spot; return spot; } /* This sets up the table used to map from a register spot to where that spot is in the closure. */ static void setup_for_transform(lily_emit_state *emit, lily_function_val *f, int is_backing) { int next_reg_spot = emit->scope_block->next_reg_spot; if (emit->transform_size < emit->scope_block->next_reg_spot) { emit->transform_table = lily_realloc(emit->transform_table, next_reg_spot * sizeof(*emit->transform_table)); emit->transform_size = emit->scope_block->next_reg_spot; } memset(emit->transform_table, UINT16_MAX, next_reg_spot * sizeof(*emit->transform_table)); lily_var *func_var = emit->scope_block->scope_var; uint16_t line_num = func_var->line_num; uint16_t local_count = func_var->type->subtype_count - 1; uint16_t i, count = 0; for (i = 0; i < lily_u16_pos(emit->closure_spots); i += 2) { if (lily_u16_get(emit->closure_spots, i + 1) == emit->function_depth) { uint16_t spot = lily_u16_get(emit->closure_spots, i); if (spot < local_count) { /* Make sure this parameter always exists in the closure. */ lily_u16_write_4(emit->closure_aux_code, o_closure_set, i / 2, spot, line_num); } emit->transform_table[spot] = i / 2; count++; /* This prevents other closures at this level from thinking this local belongs to them. */ lily_u16_set_at(emit->closure_spots, i + 1, UINT16_MAX); } } /* If there are locals in one of the inner functions, write them down. This is later used by the vm to make sure the cells of inner functions are fresh. */ if (is_backing == 0 && count) { uint16_t *locals = lily_malloc((count + 1) * sizeof(*locals)); locals[0] = count + 1; int pos = 1; for (i = 0;i < next_reg_spot;i++) { if (emit->transform_table[i] != UINT16_MAX) { locals[pos] = i; pos++; } } f->proto->locals = locals; } } /* This takes a buffer (it's always the patch buffer) and checks for a record of 'dest' inside somewhere. If not found, then 'dest' is added, along with a space after it. These pairings are later used to map from the original destination to the new destination. This function ensures that jumps that are added are kept in order from lowest to highest. The reason for that is it makes it easier for closure transform to step through them. This is a helper for closure transform: Nothing else should use it. */ static void maybe_add_jump(lily_buffer_u16 *buffer, int i, int dest) { int end = lily_u16_pos(buffer); for (;i < end;i += 2) { int jump = lily_u16_get(buffer, i); /* Make it so jumps are in order from lowest to highest. This allows the transform pass to do a check-free increase of the check position when a spot is found. */ if (jump > dest) { lily_u16_inject(buffer, i, 0); lily_u16_inject(buffer, i, dest); return; } else if (jump == dest) return; } lily_u16_write_2(buffer, dest, 0); } /* This is an ugly function that creates a code iter to determine how many transforms that an op performed. This information is used when patching the jump to an opcode, so that the jump is pulled back to account for o_closure_get transforms. */ static int count_transforms(lily_emit_state *emit, int start) { lily_code_iter ci; lily_ci_init(&ci, emit->code->data, start, lily_u16_pos(emit->code)); lily_ci_next(&ci); uint16_t *buffer = ci.buffer; uint16_t *transform_table = emit->transform_table; lily_opcode op = buffer[ci.offset]; int pos = ci.offset + 1; int count = 0; if (op == o_call_register && transform_table[buffer[pos]] != UINT16_MAX) count++; pos += ci.special_1 + ci.counter_2; if (ci.inputs_3) { int i; for (i = 0;i < ci.inputs_3;i++) { if (transform_table[buffer[pos + i]] != UINT16_MAX) count++; } } return count; } uint16_t iter_for_first_line(lily_emit_state *emit, int pos) { uint16_t result = 0; lily_code_iter ci; lily_ci_init(&ci, emit->code->data, pos, lily_u16_pos(emit->code)); while (lily_ci_next(&ci)) { if (ci.line_6) { pos = ci.offset + ci.round_total - ci.line_6; result = ci.buffer[pos]; break; } } return result; } /* This function is called to transform the currently available segment of code (emit->block->code_start up to emit->code_pos) into code that will work for closures. */ static void perform_closure_transform(lily_emit_state *emit, lily_block *scope_block, lily_function_val *f) { if (emit->closure_aux_code == NULL) emit->closure_aux_code = lily_new_buffer_u16(8); else lily_u16_set_pos(emit->closure_aux_code, 0); uint16_t iter_start = emit->block->code_start; int is_backing = (scope_block->flags & BLOCK_CLOSURE_ORIGIN); uint16_t first_line = iter_for_first_line(emit, iter_start); if (is_backing) { /* Put the closure into a new register so the gc can't accidentally delete it. */ uint16_t closure_reg = scope_block->next_reg_spot; scope_block->next_reg_spot++; lily_u16_write_4(emit->closure_aux_code, o_closure_new, lily_u16_pos(emit->closure_spots) / 2, closure_reg, first_line); lily_storage *self = scope_block->self; if (self && self->closure_spot != UINT16_MAX) { /* Class constructors don't allow closing over their self and enums don't have a constructor. If the backing closure has self inside, then it has to come from a class/enum method. Those methods will always have self as the first spot, hence the zero. */ lily_u16_write_4(emit->closure_aux_code, o_closure_set, self->closure_spot, 0, first_line); } } else if (emit->block->self) { /* Pull self from the closure into the proper register. */ lily_storage *block_self = emit->block->self; lily_u16_write_4(emit->closure_aux_code, o_closure_get, block_self->closure_spot, block_self->reg_spot, first_line); } setup_for_transform(emit, f, is_backing); if (is_backing) lily_u16_set_pos(emit->closure_spots, 0); lily_code_iter ci; lily_ci_init(&ci, emit->code->data, iter_start, lily_u16_pos(emit->code)); uint16_t *transform_table = emit->transform_table; uint16_t jump_adjust = 0; /* If the input at the position given by 'x' is within the closure, then write an instruction to fetch it from the closure first. This makes sure that if this local is in the closure, it needs to read from the closure first so that any assignment to it as an upvalue will be reflected. */ #define MAYBE_TRANSFORM_INPUT(x, z) \ { \ uint16_t id = transform_table[buffer[x]]; \ if (id != UINT16_MAX) { \ lily_u16_write_4(emit->closure_aux_code, z, id, \ buffer[x], first_line); \ jump_adjust += 4; \ } \ } uint16_t *buffer = ci.buffer; uint16_t patch_start = lily_u16_pos(emit->patches); int i, pos; /* Begin by creating a listing of all jump destinations, which is organized from lowest to highest. Each entry has a spot after it to hold where that jump is patched to. The spots will be filled during transformation. */ while (lily_ci_next(&ci)) { if (ci.jumps_5) { int stop = ci.offset + ci.round_total - ci.line_6; for (i = stop - ci.jumps_5;i < stop;i++) { int jump = (int16_t)buffer[i]; /* Catching opcodes write a jump to 0 to let vm know that there is no next catch branch. Do not patch those. */ if (jump == 0) continue; maybe_add_jump(emit->patches, patch_start, ci.offset + jump); } } } /* Add an impossible jump to act as a terminator. */ lily_u16_write_2(emit->patches, UINT16_MAX, 0); uint16_t patch_stop = lily_u16_pos(emit->patches); uint16_t patch_iter = patch_start; uint16_t next_jump = lily_u16_get(emit->patches, patch_iter); lily_ci_init(&ci, emit->code->data, iter_start, lily_u16_pos(emit->code)); while (lily_ci_next(&ci)) { int output_start = 0; lily_opcode op = buffer[ci.offset]; /* +1 to skip over the opcode itself. */ pos = ci.offset + 1; if (ci.special_1) { switch (op) { case o_call_register: MAYBE_TRANSFORM_INPUT(pos, o_closure_get) default: pos += ci.special_1; break; } } pos += ci.counter_2; if (ci.inputs_3) { for (i = 0;i < ci.inputs_3;i++) { MAYBE_TRANSFORM_INPUT(pos + i, o_closure_get) } pos += ci.inputs_3; } if (ci.outputs_4) { output_start = pos; pos += ci.outputs_4; } i = ci.offset; if (i == next_jump) { /* This op is a jump target. Write where it transform put it, and setup to look for the next jump. Remember that there's an impossible jump as the terminator, so there's no need for a length check here. */ lily_u16_set_at(emit->patches, patch_iter + 1, lily_u16_pos(emit->closure_aux_code)); patch_iter += 2; next_jump = lily_u16_get(emit->patches, patch_iter); } int stop = ci.offset + ci.round_total - ci.jumps_5 - ci.line_6; for (;i < stop;i++) lily_u16_write_1(emit->closure_aux_code, buffer[i]); if (ci.jumps_5) { for (i = 0;i < ci.jumps_5;i++) { /* This is the absolute position of this jump, but within the original buffer. */ int distance = (int16_t)buffer[stop + i]; /* Exceptions write 0 as their last jump to note that handling should stop. Don't patch those 0's. */ if (distance) { int destination = ci.offset + distance; /* Jumps are recorded in pairs. The next pass will take the resulting position, and calculate how to get to the destination. Do note: Jumps are relative to the position of the opcode, for the sake of the vm. So include an offset from the opcode for use in the calculation. */ lily_u16_write_2(emit->patches, lily_u16_pos(emit->closure_aux_code), ci.round_total - ci.jumps_5 - ci.line_6 + i); lily_u16_write_1(emit->closure_aux_code, destination); } else lily_u16_write_1(emit->closure_aux_code, 0); } } if (ci.line_6) lily_u16_write_1(emit->closure_aux_code, buffer[pos]); if (ci.outputs_4) { int output_stop = output_start + ci.outputs_4; for (i = output_start;i < output_stop;i++) { MAYBE_TRANSFORM_INPUT(i, o_closure_set) } } } /* It's time to patch the unfixed jumps, if there are any. The area from patch_stop to the ending position contains jumps to be fixed. */ uint16_t j; for (j = patch_stop;j < lily_u16_pos(emit->patches);j += 2) { /* This is where, in the new code, that the jump is located. */ int aux_pos = lily_u16_get(emit->patches, j); /* This has been set to an absolute destination in old code. */ int original = lily_u16_get(emit->closure_aux_code, aux_pos); int k; for (k = patch_start;k < patch_stop;k += 2) { if (original == lily_u16_get(emit->patches, k)) { int tx_offset = count_transforms(emit, original) * 4; /* Note that this is going to be negative for back jumps. */ int new_jump = /* The new destination */ lily_u16_get(emit->patches, k + 1) /* The location */ - aux_pos /* The distance between aux_pos and its opcode. */ + lily_u16_get(emit->patches, j + 1) /* How far to go back to include upvalue reads. */ - tx_offset; lily_u16_set_at(emit->closure_aux_code, aux_pos, (int16_t)new_jump); break; } } } lily_u16_set_pos(emit->patches, patch_start); } /*** * __ __ _ _ * | \/ | __ _| |_ ___| |__ * | |\/| |/ _` | __/ __| '_ \ * | | | | (_| | || (__| | | | * |_| |_|\__,_|\__\___|_| |_| * */ static void eval_enforce_value(lily_emit_state *, lily_ast *, lily_type *); /** Match blocks are given a symbol that each case checks for being of a certain class. For enums, this pattern matching can include extraction of the contents of variants if the variants have values. For classes, match will assign class contents to a given variable. The code here takes advantage of the following: * At vm time, enum values have the id of a variant. Each case starts with o_jump_if_not_class, and each of those instructions links to the next one. The id test is against the identity of the variant or class that the user is interested in. * Variants and user classes have the same layout, so o_property_get is written to extract variant values that the user is interested in. **/ static int is_duplicate_case(lily_emit_state *emit, lily_class *cls) { uint16_t i; uint16_t stop = lily_u16_pos(emit->match_cases); uint16_t cls_id = cls->id; lily_buffer_u16 *cases = emit->match_cases; int result = 0; for (i = emit->block->match_case_start;i < stop;i++) { uint16_t match_case = lily_u16_get(cases, i); if (match_case == cls_id) { result = 1; break; } } return result; } void lily_emit_write_class_case(lily_emit_state *emit, lily_var *var) { lily_u16_write_4(emit->code, o_assign, emit->block->match_reg, var->reg_spot, *emit->lex_linenum); } void lily_emit_write_variant_case(lily_emit_state *emit, lily_var *var, uint16_t index) { lily_u16_write_5(emit->code, o_property_get, index, emit->block->match_reg, var->reg_spot, *emit->lex_linenum); } lily_type *lily_emit_type_for_variant(lily_emit_state *emit, lily_variant_class *cls) { lily_type *build_type = cls->build_type; lily_type *match_type = emit->block->match_type; lily_type *result = lily_ts_resolve_by_second(emit->ts, match_type, build_type); return result; } int lily_emit_try_match_switch(lily_emit_state *emit, lily_class *cls) { lily_block *block = emit->block; if (is_duplicate_case(emit, cls) || block->flags & BLOCK_FINAL_BRANCH) return 0; uint16_t match_reg = block->match_reg; if ((block->flags & BLOCK_MULTI_MATCH) == 0) lily_emit_branch_switch(emit); else { block->flags &= ~BLOCK_MULTI_MATCH; /* This is the jump of the last o_jump_if_not_class. */ uint16_t patch = lily_u16_pop(emit->patches); uint16_t adjust = lily_u16_get(emit->code, patch); /* If this branch succeds, it needs to jump to the code section. Write a jump to be patched later. */ lily_u16_write_2(emit->code, o_jump, 1); lily_u16_write_1(emit->patches, lily_u16_pos(emit->code) - 1); /* Fix the last o_jump_if_not_class to go here where the new one is. */ if (patch != 0) lily_u16_set_at(emit->code, patch, lily_u16_pos(emit->code) + adjust - patch); } lily_u16_write_1(emit->match_cases, cls->id); /* If this isn't the class, jump to the next branch (or exit). */ lily_u16_write_4(emit->code, o_jump_if_not_class, cls->id, match_reg, 3); lily_u16_write_1(emit->patches, lily_u16_pos(emit->code) - 1); if (cls->item_kind & ITEM_IS_VARIANT) { uint16_t total = lily_u16_pos(emit->match_cases); uint16_t count = total - block->match_case_start; if (count == cls->parent->variant_size) block->flags |= BLOCK_FINAL_BRANCH; } return 1; } void lily_emit_multi_match_mark(lily_emit_state *emit) { emit->block->flags |= BLOCK_MULTI_MATCH; } void lily_emit_multi_match_end_group(lily_emit_state *emit, uint16_t count) { /* All cases of a multi match case have been found, so code is coming next. The last patch is the o_jump_if_not_class of the last case, and it needs to be patched to the next match case (or the exit). Behind it are 'count' o_jump patches that need to be patched to the code block (here). */ uint16_t stash_patch = lily_u16_pop(emit->patches); uint16_t start = lily_u16_pos(emit->patches) - count; write_patches_since(emit, start); lily_u16_write_1(emit->patches, stash_patch); } void lily_eval_match_with(lily_emit_state *emit, lily_expr_state *es) { lily_ast *ast = es->root; eval_enforce_value(emit, ast, lily_question_type); lily_type *match_type = ast->result->type; lily_class *match_class = match_type->cls; if ((match_class->item_kind & (ITEM_IS_ENUM | ITEM_CLASS_NATIVE)) == 0) lily_raise_syn(emit->raiser, "Invalid expression given.\n" "Expected: A user class or enum.\n" "Received: ^T", match_type); lily_block *block = emit->block; block->match_case_start = lily_u16_pos(emit->match_cases); block->last_exit = lily_u16_pos(emit->code); block->match_reg = ast->result->reg_spot; block->match_type = match_type; /* Branch switching expects a patch, so write a fake one to skip over. */ lily_u16_write_1(emit->patches, 0); } /*** * _ _ _ * | | | | ___| |_ __ ___ _ __ ___ * | |_| |/ _ \ | '_ \ / _ \ '__/ __| * | _ | __/ | |_) | __/ | \__ \ * |_| |_|\___|_| .__/ \___|_| |___/ * |_| */ /** These are various helping functions collected together. There's no real organization other than that. **/ static lily_proto_stack *new_proto_stack(uint16_t initial) { lily_proto_stack *result = lily_malloc(sizeof(*result)); result->data = lily_malloc(initial * sizeof(*result->data)); result->pos = 0; result->size = initial; return result; } static void free_proto_stack(lily_proto_stack *stack) { uint16_t i; /* Stop at pos instead of size because there's no eager init here. */ for (i = 0;i < stack->pos;i++) { lily_proto *p = stack->data[i]; lily_free(p->name); lily_free(p->locals); lily_free(p->code); if (p->keywords) { lily_free(p->keywords[0]); lily_free(p->keywords); } lily_free(p); } lily_free(stack->data); lily_free(stack); } static void grow_protos(lily_proto_stack *stack) { int new_size = stack->size * 2; lily_proto **new_data = lily_realloc(stack->data, sizeof(*new_data) * stack->size * 2); stack->data = new_data; stack->size = new_size; } lily_proto *lily_emit_new_proto(lily_emit_state *emit, const char *module_path, const char *class_name, const char *name) { lily_proto_stack *protos = emit->protos; if (protos->pos == protos->size) grow_protos(protos); lily_proto *p = lily_malloc(sizeof(*p)); char *proto_name; if (class_name != NULL) { if (name[0] != '<') { proto_name = lily_malloc(strlen(class_name) + strlen(name) + 2); strcpy(proto_name, class_name); strcat(proto_name, "."); strcat(proto_name, name); } else { /* Instead of Class.<new>, use just Class. */ proto_name = lily_malloc(strlen(class_name) + 1); strcpy(proto_name, class_name); } } else { proto_name = lily_malloc(strlen(name) + 1); strcpy(proto_name, name); } p->module_path = module_path; p->name = proto_name; p->locals = NULL; p->code = NULL; p->keywords = NULL; protos->data[protos->pos] = p; protos->pos++; return p; } lily_proto *lily_emit_proto_for_var(lily_emit_state *emit, lily_var *var) { lily_value *v = lily_literal_at(emit->symtab, var->reg_spot); return v->value.function->proto; } /* Check if 'type' is something that can be considered truthy/falsey. Keep this synced with the vm's o_jump_if calculation. Failure: SyntaxError is raised. */ static void ensure_valid_condition_type(lily_emit_state *emit, lily_type *type) { int cls_id = type->cls->id; if (cls_id != LILY_ID_INTEGER && cls_id != LILY_ID_DOUBLE && cls_id != LILY_ID_STRING && cls_id != LILY_ID_LIST && cls_id != LILY_ID_BOOLEAN) lily_raise_syn(emit->raiser, "^T is not a valid condition type.", type); } /* This checks to see if 'index_ast' has a type (and possibly, a value) that is a valid index for the type held by 'var_ast'. Failure: SyntaxError is raised. */ static void check_valid_subscript(lily_emit_state *emit, lily_ast *var_ast, lily_ast *index_ast) { int var_cls_id = var_ast->result->type->cls->id; if (var_cls_id == LILY_ID_LIST || var_cls_id == LILY_ID_BYTESTRING) { uint16_t index_id = index_ast->result->type->cls->id; if (index_id != LILY_ID_INTEGER && index_id != LILY_ID_BYTE) lily_raise_tree(emit->raiser, var_ast, "%s index is not an Integer or a Byte.", var_ast->result->type->cls->name); } else if (var_cls_id == LILY_ID_HASH) { lily_type *want_key = var_ast->result->type->subtypes[0]; lily_type *have_key = index_ast->result->type; if (want_key != have_key) { lily_raise_tree(emit->raiser, var_ast, "Hash index should be type '^T', not type '^T'.", want_key, have_key); } } else if (var_cls_id == LILY_ID_TUPLE) { if (index_ast->tree_type != tree_integer) { lily_raise_tree(emit->raiser, var_ast, "Tuple subscripts must be Integer literals."); } int index_value = index_ast->backing_value; lily_type *var_type = var_ast->result->type; if (index_value < 0 || index_value >= var_type->subtype_count) { lily_raise_tree(emit->raiser, var_ast, "Index %d is out of range for ^T.", index_value, var_type); } } else { lily_raise_tree(emit->raiser, var_ast, "Cannot subscript type '^T'.", var_ast->result->type); } } /* This returns the result of subscripting 'type' with the value within 'index_ast'. It should only be called once 'index_ast' has been validated with check_valid_subscript. This will not fail. */ static lily_type *get_subscript_result(lily_emit_state *emit, lily_type *type, lily_ast *index_ast) { lily_type *result = NULL; if (type->cls->id == LILY_ID_LIST) result = type->subtypes[0]; else if (type->cls->id == LILY_ID_HASH) result = type->subtypes[1]; else if (type->cls->id == LILY_ID_TUPLE) { /* check_valid_subscript ensures that this is safe. */ int literal_index = index_ast->backing_value; result = type->subtypes[literal_index]; } else if (type->cls->id == LILY_ID_BYTESTRING) result = emit->symtab->byte_class->self_type; return result; } /* Since o_build_list, o_build_tuple, and o_build_hash are fairly similar (and the first two are fairly common), this function writes all of them. This function takes a tree, and will walk it up to 'num_values' times. This function does not create a storage. Instead, the caller is expected to provide a storage of the appropriate type. Said storage should have a spot that is 'reg_spot'. */ static void write_build_op(lily_emit_state *emit, uint16_t opcode, lily_ast *first_arg, uint16_t line_num, uint16_t num_values, lily_storage *s) { lily_ast *arg; lily_u16_write_prep(emit->code, 5 + num_values); lily_u16_write_1(emit->code, opcode); if (opcode == o_build_hash) /* The vm the key's id to decide what hashing functions to use. */ lily_u16_write_1(emit->code, s->type->subtypes[0]->cls->id); lily_u16_write_1(emit->code, num_values); for (arg = first_arg; arg != NULL; arg = arg->next_arg) lily_u16_write_1(emit->code, arg->result->reg_spot); lily_u16_write_2(emit->code, s->reg_spot, line_num); } /* This checks that 'sym' (either a var or a property) can be used within the current scope. If it cannot be, then SyntaxError is raised. */ static void ensure_valid_scope(lily_emit_state *emit, lily_ast *ast) { lily_named_sym *sym = (lily_named_sym *)ast->sym; if (sym->flags & (SYM_SCOPE_PRIVATE | SYM_SCOPE_PROTECTED)) { lily_class *block_cls = emit->block->class_entry; /* Vars and properties have this at the same offset. */ lily_class *parent = sym->parent; int is_private = (sym->flags & SYM_SCOPE_PRIVATE); char *name = sym->name; if ((is_private && block_cls != parent) || (is_private == 0 && (block_cls == NULL || lily_class_greater_eq(parent, block_cls) == 0))) { char *scope_name = is_private ? "private" : "protected"; lily_raise_tree(emit->raiser, ast, "%s.%s is marked %s, and not available here.", parent->name, name, scope_name); } } } static int can_optimize_out_assignment(lily_ast *ast) { lily_ast *right_tree = ast->right; int can_optimize = 0; while (right_tree->tree_type == tree_parenth) right_tree = right_tree->arg_start; /* Keep these conditions away from each other since each has a different reason why optimization can't be done. */ if (right_tree->tree_type == tree_local_var) /* Can't skip basic assignments. */ ; else if (right_tree->tree_type == tree_binary) { uint8_t op = right_tree->op; if (IS_ASSIGN_TOKEN(op)) ; else if (op == tk_logical_and || op == tk_logical_or || op == tk_eq_eq || op == tk_not_eq || IS_COMPARE_TOKEN(op)) /* These finish by writing a jump table and two writes. They can't be optimized because the optimize only covers one write. */ ; else can_optimize = 1; } else can_optimize = 1; return can_optimize; } /* This is a simple function that checks if the result in 'right' is suitable to be the type required by 'want_type'. Returns 1 if yes, 0 if no. */ static int type_matchup(lily_emit_state *emit, lily_type *want_type, lily_ast *right) { int ret; lily_type *right_type = right->result->type; if (want_type == right_type || lily_ts_type_greater_eq(emit->ts, want_type, right_type)) ret = 1; else ret = 0; return ret; } /*** * _____ * | ____|_ __ _ __ ___ _ __ ___ * | _| | '__| '__/ _ \| '__/ __| * | |___| | | | | (_) | | \__ \ * |_____|_| |_| \___/|_| |___/ * */ static void inconsistent_type_error(lily_emit_state *emit, lily_ast *ast, lily_type *expect, const char *context) { lily_raise_tree(emit->raiser, ast, "%s do not have a consistent type.\n" "Expected Type: ^T\n" "Received Type: ^T", context, expect, ast->result->type); } static void bad_assign_error(lily_emit_state *emit, lily_ast *ast, lily_type *left_type, lily_type *right_type) { lily_raise_tree(emit->raiser, ast, "Cannot assign type '^T' to type '^T'.", right_type, left_type); } static void incomplete_type_assign_error(lily_emit_state *emit, lily_ast *ast, lily_type *right_type) { lily_raise_tree(emit->raiser, ast, "Right side of assignment is incomplete type '^T'.", right_type); } static void add_call_name_to_msgbuf(lily_emit_state *emit, lily_msgbuf *msgbuf, lily_ast *ast) { lily_item *item = ast->item; if (item->item_kind == ITEM_DEFINE) { lily_var *v = (lily_var *)item; lily_value *val = lily_literal_at(emit->symtab, v->reg_spot); lily_proto *p = val->value.function->proto; lily_mb_add(msgbuf, p->name); } else if (item->item_kind == ITEM_VAR) { lily_var *v = (lily_var *)item; lily_mb_add(msgbuf, v->name); } else if (item->item_kind == ITEM_PROPERTY) { lily_prop_entry *p = (lily_prop_entry *)ast->item; lily_mb_add_fmt(msgbuf, "%s.%s", p->parent->name, p->name); } else if (item->item_kind & ITEM_IS_VARIANT) { lily_variant_class *v = (lily_variant_class *)ast->item; lily_mb_add(msgbuf, v->name); } else lily_mb_add(msgbuf, "(anonymous)"); } /* This is called when call processing has an argument of the wrong type. This generates a syntax error with the call name if that can be located. This assumes 'index' is 0-based (argument 0 being the first argument to the function given. If 'index' exceeds the number of types available, it's assumed that the source is varargs and the last type is used for display. */ static void error_bad_arg(lily_emit_state *emit, lily_ast *ast, lily_type *call_type, int index, lily_type *got) { lily_type *expected; if ((call_type->flags & TYPE_IS_VARARGS) == 0 || index < call_type->subtype_count - 2) expected = call_type->subtypes[index + 1]; else { /* Varargs are represented by having the last argument be a `List` of the type that's wanted. That's the type that was really wanted. */ expected = call_type->subtypes[call_type->subtype_count - 1]; expected = expected->subtypes[0]; } if (expected->flags & TYPE_IS_UNRESOLVED) expected = lily_ts_resolve(emit->ts, expected); lily_msgbuf *msgbuf = emit->raiser->aux_msgbuf; lily_mb_flush(msgbuf); lily_mb_add_fmt(msgbuf, "Argument #%d to ", index + 1); add_call_name_to_msgbuf(emit, msgbuf, ast); lily_mb_add_fmt(msgbuf, " is invalid:\n" "Expected Type: ^T\n" "Received Type: ^T", expected, got); lily_raise_tree(emit->raiser, ast, lily_mb_raw(msgbuf)); } /* This is called when the tree given doesn't have enough arguments. ast: The tree receiving the call. count: The real # of arguments that 'ast' was given. min: The minimum number allowed (lower than max in case of optargs). max: The maximum allowed (UINT16_MAX in case of varargs). This is typically called automatically by argument handling if the count is wrong. */ static void error_argument_count(lily_emit_state *emit, lily_ast *ast, uint16_t count, uint16_t min, uint16_t max) { /* This prints out the number sent, as well as the range of valid counts. There are four possibilities, with the last one being exclusively for a variant that requires arguments. (# for n) (# for n+) (# for n..m) (none for n) */ const char *div_str = ""; char arg_str[8], min_str[8] = "", max_str[8] = ""; if (count != 0 || ast->tree_type != tree_variant) snprintf(arg_str, sizeof(arg_str), "%d", count); else strncpy(arg_str, "none", sizeof(arg_str)); snprintf(min_str, sizeof(min_str), "%d", min); if (min == max) div_str = ""; else if (max == UINT16_MAX) div_str = "+"; else { div_str = ".."; snprintf(max_str, sizeof(max_str), "%d", max); } lily_msgbuf *msgbuf = emit->raiser->aux_msgbuf; lily_mb_flush(msgbuf); lily_mb_add(msgbuf, "Wrong number of arguments to "); add_call_name_to_msgbuf(emit, msgbuf, ast); lily_mb_add_fmt(msgbuf, " (%s for %s%s%s).", arg_str, min_str, div_str, max_str); lily_raise_tree(emit->raiser, ast, lily_mb_raw(msgbuf)); } static void error_keyarg_not_supported(lily_emit_state *emit, lily_ast *ast) { lily_msgbuf *msgbuf = emit->raiser->aux_msgbuf; lily_mb_flush(msgbuf); add_call_name_to_msgbuf(emit, msgbuf, ast); if (ast->sym->item_kind == ITEM_DEFINE) lily_mb_add(msgbuf, " does not specify any keyword arguments."); else lily_mb_add(msgbuf, " is not capable of receiving keyword arguments."); lily_raise_tree(emit->raiser, ast, lily_mb_raw(msgbuf)); } static void error_keyarg_not_valid(lily_emit_state *emit, lily_ast *ast, lily_ast *arg) { char *key_name = lily_sp_get(emit->expr_strings, arg->left->pile_pos); lily_msgbuf *msgbuf = emit->raiser->aux_msgbuf; lily_mb_flush(msgbuf); add_call_name_to_msgbuf(emit, msgbuf, ast); lily_mb_add_fmt(msgbuf, " does not have a keyword named ':%s'.", key_name); lily_raise_tree(emit->raiser, arg, lily_mb_raw(msgbuf)); } static void error_keyarg_duplicate(lily_emit_state *emit, lily_ast *ast, lily_ast *arg) { char *key_name = lily_sp_get(emit->expr_strings, arg->left->pile_pos); lily_msgbuf *msgbuf = emit->raiser->aux_msgbuf; lily_mb_flush(msgbuf); lily_mb_add(msgbuf, "Call to "); add_call_name_to_msgbuf(emit, msgbuf, ast); lily_mb_add_fmt(msgbuf, " has multiple values for parameter ':%s'.", key_name); lily_raise_tree(emit->raiser, arg, lily_mb_raw(msgbuf)); } static void error_keyarg_before_posarg(lily_emit_state *emit, lily_ast *arg) { lily_raise_tree(emit->raiser, arg, "Positional argument after keyword argument."); } static void error_keyarg_missing_params(lily_emit_state *emit, lily_ast *ast, lily_type *call_type, char **keywords) { uint16_t i; uint16_t stop = call_type->subtype_count - 1; lily_ast *arg_start = ast->arg_start; lily_msgbuf *msgbuf = emit->raiser->aux_msgbuf; lily_type **arg_types = call_type->subtypes; lily_mb_flush(msgbuf); lily_mb_add(msgbuf, "Call to "); add_call_name_to_msgbuf(emit, msgbuf, ast); lily_mb_add(msgbuf, " is missing parameters:"); if (call_type->flags & TYPE_IS_VARARGS) stop--; for (i = 0;i != stop;i++) { lily_ast *arg_iter = arg_start; int skip = 0; for (;arg_iter != NULL;arg_iter = arg_iter->next_arg) { if (arg_iter->keyword_arg_pos != i) continue; if (arg_start == arg_iter) arg_start = arg_start->next_arg; skip = 1; break; } if (skip) continue; lily_type *t = arg_types[i + 1]; if (t->cls->id == LILY_ID_OPTARG) break; char *key = keywords[i]; if (key[0] != '\0') lily_mb_add_fmt(msgbuf, "\n* Parameter #%d (:%s) of type ^T.", i + 1, key, t); else lily_mb_add_fmt(msgbuf, "\n* Parameter #%d of type ^T.", i + 1, t); } lily_raise_tree(emit->raiser, ast, lily_mb_raw(msgbuf)); } /*** * __ __ _ * | \/ | ___ _ __ ___ | |__ ___ _ __ ___ * | |\/| |/ _ \ '_ ` _ \| '_ \ / _ \ '__/ __| * | | | | __/ | | | | | |_) | __/ | \__ \ * |_| |_|\___|_| |_| |_|_.__/ \___|_| |___/ * */ /** Member access for classes is quite annoying. The parser doesn't have type information, so it packages them as having a tree and a name. It seems like it would be as simple as 'evaluate the tree, lookup the name', but it isn't. The name could be a method that needs to be dynaloaded, a class member, or a method that has already been loaded. The thing that is loaded must dump a value to a storage. Problems begin with 'x.y = z' (oo/prop assign). This kind of access needs to know what type that it is targeting. Running the 'x.y' access is a waste, because it's not possible to assign to a storage (that fails if y is an integer property). Additionally, 'y' might be a property that isn't solved yet, so that's another issue. 'x.y += z' is also tricky. **/ /* This runs an 'x.y' kind of access. The inner tree is evaluated, but no result is set. Instead, the ast's item is set to either the var or the storage that is returned. SyntaxError is raised if the name specified doesn't exist, or is not available in the current scope. */ static void eval_oo_access_for_item(lily_emit_state *emit, lily_ast *ast) { if (ast->arg_start->tree_type != tree_local_var) eval_tree(emit, ast->arg_start, lily_question_type); lily_class *lookup_class = ast->arg_start->result->type->cls; char *oo_name = lily_sp_get(emit->expr_strings, ast->pile_pos); lily_item *item = lily_find_or_dl_member(emit->parser, lookup_class, oo_name); if (item == NULL) { lily_raise_tree(emit->raiser, ast->arg_start, "Class %s does not have a member named %s.", lookup_class->name, oo_name); } if (item->item_kind == ITEM_PROPERTY && ast->arg_start->tree_type == tree_self) lily_raise_tree(emit->raiser, ast->arg_start, "Use @<name> to get/set properties, not self.<name>."); else if (item->item_kind & ITEM_IS_VARIANT) lily_raise_tree(emit->raiser, ast->arg_start, "Not allowed to access a variant through an enum instance."); ast->item = item; ensure_valid_scope(emit, ast); } /* This is called on oo trees that have been evaluated and which contain a property. This will solve the property's type using the information of the inner tree. The inner tree will not be evaluated again. */ static lily_type *get_solved_property_type(lily_emit_state *emit, lily_ast *ast) { lily_type *property_type = ast->property->type; if (property_type->flags & TYPE_IS_UNRESOLVED) { property_type = lily_ts_resolve_by_second(emit->ts, ast->arg_start->result->type, property_type); } return property_type; } /* This is called after eval_oo_access_for_item. It dumps the property or var to a storage. */ static void oo_property_read(lily_emit_state *emit, lily_ast *ast) { lily_prop_entry *prop = ast->property; lily_type *type = get_solved_property_type(emit, ast); lily_storage *result = get_storage(emit, type); /* This function is only called on trees of type tree_oo_access which have a property into the ast's item. */ lily_u16_write_5(emit->code, o_property_get, prop->id, ast->arg_start->result->reg_spot, result->reg_spot, ast->line_num); /* Properties are assignable if their source is assignable. */ if ((ast->arg_start->result->flags & SYM_NOT_ASSIGNABLE) == 0) result->flags &= ~SYM_NOT_ASSIGNABLE; ast->result = (lily_sym *)result; } /* This is the actual handler for simple 'x.y' accesses. It doesn't do assigns though. */ static void eval_oo_access(lily_emit_state *emit, lily_ast *ast) { eval_oo_access_for_item(emit, ast); /* An 'x.y' access will either yield a property or a class method. */ if (ast->item->item_kind == ITEM_PROPERTY) oo_property_read(emit, ast); else { lily_storage *result = get_storage(emit, ast->sym->type); lily_u16_write_4(emit->code, o_load_readonly, ast->sym->reg_spot, result->reg_spot, ast->line_num); ast->result = (lily_sym *)result; } } /*** * _____ _____ _ * |_ _| __ ___ ___ | ____|_ ____ _| | * | || '__/ _ \/ _ \ | _| \ \ / / _` | | * | || | | __/ __/ | |___ \ V / (_| | | * |_||_| \___|\___| |_____| \_/ \__,_|_| * */ /** Here are most of the functions related to evaluating trees. **/ static void eval_assign(lily_emit_state *, lily_ast *); static void eval_func_pipe(lily_emit_state *, lily_ast *, lily_type *); static void eval_logical_op(lily_emit_state *, lily_ast *); static void eval_plus_plus(lily_emit_state *, lily_ast *); static void eval_compare_op(lily_emit_state *emit, lily_ast *ast, int fold) { if (ast->left->tree_type != tree_local_var) eval_tree(emit, ast->left, lily_question_type); if (ast->right->tree_type != tree_local_var) eval_tree(emit, ast->right, ast->left->result->type); lily_sym *left = ast->left->result; lily_sym *right = ast->right->result; uint16_t left_id = left->type->cls->id; uint16_t op = ast->op; uint16_t opcode = UINT16_MAX; if (op == tk_eq_eq) opcode = o_compare_eq; else if (op == tk_not_eq) opcode = o_compare_not_eq; else if (left_id == LILY_ID_BYTE || left_id == LILY_ID_DOUBLE || left_id == LILY_ID_INTEGER || left_id == LILY_ID_STRING) { if (op == tk_lt_eq) { lily_sym *temp = right; right = left; left = temp; opcode = o_compare_greater_eq; } else
{'addition_count': 21, 'commit_subject': 'Emitter: Have functions try to use the storages of their args first.x', 'deletion_count': 2, 'file_extension': '.c', 'lang': 'c', 'license': 'mit', 'repo_name': 'FascinatedBox/lily'}