| /* SPDX-License-Identifier: GPL-2.0 */ |
| /* |
| * Generate opcode table initializers for the in-kernel disassembler. |
| * |
| * Copyright IBM Corp. 2017 |
| * |
| */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <stdio.h> |
| |
| #define STRING_SIZE_MAX 20 |
| |
| struct insn_type { |
| unsigned char byte; |
| unsigned char mask; |
| char **format; |
| }; |
| |
| struct insn { |
| struct insn_type *type; |
| char opcode[STRING_SIZE_MAX]; |
| char name[STRING_SIZE_MAX]; |
| char upper[STRING_SIZE_MAX]; |
| char format[STRING_SIZE_MAX]; |
| unsigned int name_len; |
| }; |
| |
| struct insn_group { |
| struct insn_type *type; |
| int offset; |
| int count; |
| char opcode[2]; |
| }; |
| |
| struct insn_format { |
| char *format; |
| int type; |
| }; |
| |
| struct gen_opcode { |
| struct insn *insn; |
| int nr; |
| struct insn_group *group; |
| int nr_groups; |
| }; |
| |
| /* |
| * Table of instruction format types. Each opcode is defined with at |
| * least one byte (two nibbles), three nibbles, or two bytes (four |
| * nibbles). |
| * The byte member of each instruction format type entry defines |
| * within which byte of an instruction the third (and fourth) nibble |
| * of an opcode can be found. The mask member is the and-mask that |
| * needs to be applied on this byte in order to get the third (and |
| * fourth) nibble of the opcode. |
| * The format array defines all instruction formats (as defined in the |
| * Principles of Operation) which have the same position of the opcode |
| * nibbles. |
| * A special case are instruction formats with 1-byte opcodes. In this |
| * case the byte member always is zero, so that the mask is applied on |
| * the (only) byte that contains the opcode. |
| */ |
| static struct insn_type insn_type_table[] = { |
| { |
| .byte = 0, |
| .mask = 0xff, |
| .format = (char *[]) { |
| "MII", |
| "RR", |
| "RS", |
| "RSI", |
| "RX", |
| "SI", |
| "SMI", |
| "SS", |
| NULL, |
| }, |
| }, |
| { |
| .byte = 1, |
| .mask = 0x0f, |
| .format = (char *[]) { |
| "RI", |
| "RIL", |
| "SSF", |
| NULL, |
| }, |
| }, |
| { |
| .byte = 1, |
| .mask = 0xff, |
| .format = (char *[]) { |
| "E", |
| "IE", |
| "RRE", |
| "RRF", |
| "RRR", |
| "S", |
| "SIL", |
| "SSE", |
| NULL, |
| }, |
| }, |
| { |
| .byte = 5, |
| .mask = 0xff, |
| .format = (char *[]) { |
| "RIE", |
| "RIS", |
| "RRS", |
| "RSE", |
| "RSL", |
| "RSY", |
| "RXE", |
| "RXF", |
| "RXY", |
| "SIY", |
| "VRI", |
| "VRR", |
| "VRS", |
| "VRV", |
| "VRX", |
| "VSI", |
| NULL, |
| }, |
| }, |
| }; |
| |
| static struct insn_type *insn_format_to_type(char *format) |
| { |
| char tmp[STRING_SIZE_MAX]; |
| char *base_format, **ptr; |
| int i; |
| |
| strcpy(tmp, format); |
| base_format = tmp; |
| base_format = strsep(&base_format, "_"); |
| for (i = 0; i < sizeof(insn_type_table) / sizeof(insn_type_table[0]); i++) { |
| ptr = insn_type_table[i].format; |
| while (*ptr) { |
| if (!strcmp(base_format, *ptr)) |
| return &insn_type_table[i]; |
| ptr++; |
| } |
| } |
| exit(EXIT_FAILURE); |
| } |
| |
| static void read_instructions(struct gen_opcode *desc) |
| { |
| struct insn insn; |
| int rc, i; |
| |
| while (1) { |
| rc = scanf("%s %s %s", insn.opcode, insn.name, insn.format); |
| if (rc == EOF) |
| break; |
| if (rc != 3) |
| exit(EXIT_FAILURE); |
| insn.type = insn_format_to_type(insn.format); |
| insn.name_len = strlen(insn.name); |
| for (i = 0; i <= insn.name_len; i++) |
| insn.upper[i] = toupper((unsigned char)insn.name[i]); |
| desc->nr++; |
| desc->insn = realloc(desc->insn, desc->nr * sizeof(*desc->insn)); |
| if (!desc->insn) |
| exit(EXIT_FAILURE); |
| desc->insn[desc->nr - 1] = insn; |
| } |
| } |
| |
| static int cmpformat(const void *a, const void *b) |
| { |
| return strcmp(((struct insn *)a)->format, ((struct insn *)b)->format); |
| } |
| |
| static void print_formats(struct gen_opcode *desc) |
| { |
| char *format; |
| int i, count; |
| |
| qsort(desc->insn, desc->nr, sizeof(*desc->insn), cmpformat); |
| format = ""; |
| count = 0; |
| printf("enum {\n"); |
| for (i = 0; i < desc->nr; i++) { |
| if (!strcmp(format, desc->insn[i].format)) |
| continue; |
| count++; |
| format = desc->insn[i].format; |
| printf("\tINSTR_%s,\n", format); |
| } |
| printf("}; /* %d */\n\n", count); |
| } |
| |
| static int cmp_long_insn(const void *a, const void *b) |
| { |
| return strcmp(((struct insn *)a)->name, ((struct insn *)b)->name); |
| } |
| |
| static void print_long_insn(struct gen_opcode *desc) |
| { |
| struct insn *insn; |
| int i, count; |
| |
| qsort(desc->insn, desc->nr, sizeof(*desc->insn), cmp_long_insn); |
| count = 0; |
| printf("enum {\n"); |
| for (i = 0; i < desc->nr; i++) { |
| insn = &desc->insn[i]; |
| if (insn->name_len < 6) |
| continue; |
| printf("\tLONG_INSN_%s,\n", insn->upper); |
| count++; |
| } |
| printf("}; /* %d */\n\n", count); |
| |
| printf("#define LONG_INSN_INITIALIZER { \\\n"); |
| for (i = 0; i < desc->nr; i++) { |
| insn = &desc->insn[i]; |
| if (insn->name_len < 6) |
| continue; |
| printf("\t[LONG_INSN_%s] = \"%s\", \\\n", insn->upper, insn->name); |
| } |
| printf("}\n\n"); |
| } |
| |
| static void print_opcode(struct insn *insn, int nr) |
| { |
| char *opcode; |
| |
| opcode = insn->opcode; |
| if (insn->type->byte != 0) |
| opcode += 2; |
| printf("\t[%4d] = { .opfrag = 0x%s, .format = INSTR_%s, ", nr, opcode, insn->format); |
| if (insn->name_len < 6) |
| printf(".name = \"%s\" ", insn->name); |
| else |
| printf(".offset = LONG_INSN_%s ", insn->upper); |
| printf("}, \\\n"); |
| } |
| |
| static void add_to_group(struct gen_opcode *desc, struct insn *insn, int offset) |
| { |
| struct insn_group *group; |
| |
| group = desc->group ? &desc->group[desc->nr_groups - 1] : NULL; |
| if (group && (!strncmp(group->opcode, insn->opcode, 2) || group->type->byte == 0)) { |
| group->count++; |
| return; |
| } |
| desc->nr_groups++; |
| desc->group = realloc(desc->group, desc->nr_groups * sizeof(*desc->group)); |
| if (!desc->group) |
| exit(EXIT_FAILURE); |
| group = &desc->group[desc->nr_groups - 1]; |
| strncpy(group->opcode, insn->opcode, 2); |
| group->type = insn->type; |
| group->offset = offset; |
| group->count = 1; |
| } |
| |
| static int cmpopcode(const void *a, const void *b) |
| { |
| return strcmp(((struct insn *)a)->opcode, ((struct insn *)b)->opcode); |
| } |
| |
| static void print_opcode_table(struct gen_opcode *desc) |
| { |
| char opcode[2] = ""; |
| struct insn *insn; |
| int i, offset; |
| |
| qsort(desc->insn, desc->nr, sizeof(*desc->insn), cmpopcode); |
| printf("#define OPCODE_TABLE_INITIALIZER { \\\n"); |
| offset = 0; |
| for (i = 0; i < desc->nr; i++) { |
| insn = &desc->insn[i]; |
| if (insn->type->byte == 0) |
| continue; |
| add_to_group(desc, insn, offset); |
| if (strncmp(opcode, insn->opcode, 2)) { |
| strncpy(opcode, insn->opcode, 2); |
| printf("\t/* %.2s */ \\\n", opcode); |
| } |
| print_opcode(insn, offset); |
| offset++; |
| } |
| printf("\t/* 1-byte opcode instructions */ \\\n"); |
| for (i = 0; i < desc->nr; i++) { |
| insn = &desc->insn[i]; |
| if (insn->type->byte != 0) |
| continue; |
| add_to_group(desc, insn, offset); |
| print_opcode(insn, offset); |
| offset++; |
| } |
| printf("}\n\n"); |
| } |
| |
| static void print_opcode_table_offsets(struct gen_opcode *desc) |
| { |
| struct insn_group *group; |
| int i; |
| |
| printf("#define OPCODE_OFFSET_INITIALIZER { \\\n"); |
| for (i = 0; i < desc->nr_groups; i++) { |
| group = &desc->group[i]; |
| printf("\t{ .opcode = 0x%.2s, .mask = 0x%02x, .byte = %d, .offset = %d, .count = %d }, \\\n", |
| group->opcode, group->type->mask, group->type->byte, group->offset, group->count); |
| } |
| printf("}\n\n"); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| struct gen_opcode _desc = { 0 }; |
| struct gen_opcode *desc = &_desc; |
| |
| read_instructions(desc); |
| printf("#ifndef __S390_GENERATED_DIS_H__\n"); |
| printf("#define __S390_GENERATED_DIS_H__\n"); |
| printf("/*\n"); |
| printf(" * DO NOT MODIFY.\n"); |
| printf(" *\n"); |
| printf(" * This file was generated by %s\n", __FILE__); |
| printf(" */\n\n"); |
| print_formats(desc); |
| print_long_insn(desc); |
| print_opcode_table(desc); |
| print_opcode_table_offsets(desc); |
| printf("#endif\n"); |
| exit(EXIT_SUCCESS); |
| } |