diff --git a/src/check_asm.cpp b/src/check_asm.cpp index c4aee9b38..94b8a82dc 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -469,16 +469,65 @@ gb_internal void check_asm_specs(CheckerContext *ctx, Scope *scope, Slice ed.pin = pin; if (other_scratch != nullptr) { - // TODO(bill): subsetting parameters - // p0: u64 = %rax, - // p0b: u8 = p0, - // - // or - // - // p0: u64, // implied = %any - // p0b: u8 = p0, // the 8-bit subsection of the same register, if possible + // Width-view of another operand: `p0b: u8 = p0`. + // p0b shares p0's register, viewed at p0b's declared width. GB_ASSERT(spec->value != nullptr); - error(spec->value, "Another parameter must be assigned/paired with a scratch parameter declaration"); + + i32 src_index = -1; + auto src_group = check_asm_find_group(other_scratch, *asm_template_entity_decls, &src_index); + + // 1. The source must already exist and be a register-class operand + // (you cannot take a width-view of an immediate or memory operand). + if (src_index < 0) { + error(spec->value, "'%.*s' must refer to a previously declared parameter", LIT(other_scratch->token.string)); + } else { + auto &src = (*asm_template_entity_decls)[src_index]; + + bool src_is_reg = src_group == AsmTemplateEntityDeclParamGroup_Input || + src_group == AsmTemplateEntityDeclParamGroup_Output || + src_group == AsmTemplateEntityDeclParamGroup_Scratch; + if (src.kind == AsmTemplateEntityDecl_Immediate || src.kind == AsmTemplateEntityDecl_Memory) { + src_is_reg = false; + } + if (!src_is_reg) { + error(spec->value, "A width-view can only be taken of a register operand, not '%.*s'", LIT(other_scratch->token.string)); + } + + // 2. The view width must be a legal sub-register width and no wider + // than the source (only narrowing views exist). + i32 view_w = check_asm_operand_bit_width(type); // this decl's type (u8 -> 8) + i32 src_w = check_asm_operand_bit_width(src.entity->type); + AsmRegClass view_class = check_asm_reg_class_from_type(type); + AsmRegClass src_class = check_asm_reg_class_from_type(src.entity->type); + + if (view_class != AsmRegClass_Integer || src_class != AsmRegClass_Integer) { + error(spec->type, "Width-views are only supported for integer registers"); + } else { + switch (view_w) { + case 8: case 16: case 32: case 64: + if (view_w > src_w) { + error(spec->type, "A width-view (%d-bit) cannot be wider than its source '%.*s' (%d-bit)", + cast(int)view_w, LIT(other_scratch->token.string), cast(int)src_w); + } + break; + default: + error(spec->type, "A width-view must be an 8, 16, 32, or 64-bit integer type, got a %d-bit type", cast(int)view_w); + break; + } + } + + // 3. A view does not carry its own pin; it inherits the source's register. + if (pin.len != 0) { + error(spec->value, "A width-view cannot also be pinned to a register; it inherits the source operand's register"); + } + + ed.kind = AsmTemplateEntityDecl_Register; + ed.view_of = src_index; + ed.view_bits = view_w; + // A view is not itself an input/output/scratch slot for allocation: + // mark it so the lowering passes skip it. Reuse the Scratch group but + // with view_of >= 0 as the discriminator (see lowering note). + } } array_add(asm_template_entity_decls, ed); diff --git a/src/entity.cpp b/src/entity.cpp index 31a479eb9..446947ef0 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -192,6 +192,9 @@ struct AsmTemplateEntityDecl { i32 param_index; // index into the Proc signature's params (inputs), else -1 i32 result_index; // index into results (outputs), else -1 i32 tie; // InOut: index into operands[] of the tied output; else -1 + + i32 view_of; // total_index of the source operand this is a width-view of, else -1 + i32 view_bits; // the view width in bits, otherwise 0 }; // An Entity is a named "thing" in the language @@ -374,6 +377,8 @@ gb_internal AsmTemplateEntityDecl asm_template_entity_decl_default(Entity *entit ed.param_index = -1; ed.result_index = -1; ed.tie = -1; + ed.view_of = -1; + ed.view_bits = 0; return ed; } diff --git a/src/llvm_backend_asm.cpp b/src/llvm_backend_asm.cpp index 486cc5dcd..530106ed2 100644 --- a/src/llvm_backend_asm.cpp +++ b/src/llvm_backend_asm.cpp @@ -49,9 +49,26 @@ struct lbAsmGenerate { Entity *e = entity_of_node(op); auto *ed = entity_op(e); - i32 idx = op_number[ed->total_index]; - GB_ASSERT(idx >= 0); - asm_string = gb_string_append_fmt(asm_string, "$%d", idx); + if (ed->view_of >= 0) { + // Width-view of another operand (e.g. `p0b: u8 = p0`): emit the SOURCE + // operand's number with an LLVM width modifier, so both names share the + // one register the allocator chose, viewed at the requested width. + i32 idx = op_number[ed->view_of]; + GB_ASSERT(idx >= 0); + char mod = 0; + switch (ed->view_bits) { + case 8: mod = 'b'; break; // low 8-bit (al/r11b/...) + case 16: mod = 'w'; break; // 16-bit (ax/r11w/...) + case 32: mod = 'k'; break; // 32-bit (eax/r11d/...) + case 64: mod = 'q'; break; // 64-bit (rax/r11/...) + default: GB_PANIC("asm: invalid width-view size %d", ed->view_bits); break; + } + asm_string = gb_string_append_fmt(asm_string, "${%d:%c}", idx, mod); + } else { + i32 idx = op_number[ed->total_index]; + GB_ASSERT(idx >= 0); + asm_string = gb_string_append_fmt(asm_string, "$%d", idx); + } case_end; case_ast_node(mem_op, AsmMemoryOperand, op); asm_string = this->write_memory_operand(asm_string, op_number, mem_op, flags&~WriteOperandFlag_PrintPrefixes); @@ -218,7 +235,7 @@ struct lbAsmGenerate_amd64 : lbAsmGenerate { auto ret_types = array_make(temporary_allocator(), 0, ops->count); // Per-operand bookkeeping, indexed the same as `ops` (via total_index). - auto op_number = array_make(temporary_allocator(), ops->count, ops->count); // $N, or -1 for clobbers + auto op_number = array_make(temporary_allocator(), ops->count, ops->count); // $N, or -1 for clobbers/views auto ret_slot = array_make(temporary_allocator(), ops->count, ops->count); // return-struct index, or -1 for_array(i, *ops) { op_number[i] = -1; @@ -259,6 +276,10 @@ struct lbAsmGenerate_amd64 : lbAsmGenerate { for_array(i, *ops) { AsmTemplateEntityDecl const &e = (*ops)[i]; + if (e.view_of >= 0) { + continue; // width-view: resolved to its source's operand, owns no slot + } + bool is_output = e.param_group == AsmTemplateEntityDeclParamGroup_Output; bool is_alloc_scratch = e.param_group == AsmTemplateEntityDeclParamGroup_Scratch && e.kind == AsmTemplateEntityDecl_Register @@ -292,6 +313,10 @@ struct lbAsmGenerate_amd64 : lbAsmGenerate { // Pass 2: inputs for (isize i = 0; i < ops->count; i++) { AsmTemplateEntityDecl const &e = (*ops)[i]; + + if (e.view_of >= 0) { + continue; // width-view: not its own input + } if (e.param_group != AsmTemplateEntityDeclParamGroup_Input) { continue; } @@ -383,6 +408,10 @@ struct lbAsmGenerate_amd64 : lbAsmGenerate { // output in Pass 1, so it is skipped here. for (isize i = 0; i < ops->count; i++) { AsmTemplateEntityDecl const &e = (*ops)[i]; + + if (e.view_of >= 0) { + continue; // width-view carries no clobber; its source owns the register + } if (e.param_group != AsmTemplateEntityDeclParamGroup_Scratch) { continue; } @@ -466,6 +495,9 @@ struct lbAsmGenerate_amd64 : lbAsmGenerate { for (isize i = 0; i < ops->count; i++) { AsmTemplateEntityDecl const &e = (*ops)[i]; + if (e.view_of >= 0) { + continue; // width-view: never a returned value + } if (e.param_group != AsmTemplateEntityDeclParamGroup_Output) { continue; } @@ -502,4 +534,4 @@ gb_internal lbValue lb_emit_asm_template_call(lbProcedure *p, Entity *entity, Ar lbAsmGenerate_amd64 generator = {}; generator.init(entity); return generator.emit_call(p, args); -} \ No newline at end of file +}