STATIC void emit_inline_xtensa_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_args, mp_parse_node_t *pn_args) {
    size_t op_len;
    const char *op_str = (const char*)qstr_data(op, &op_len);

    if (n_args == 0) {
        if (op == MP_QSTR_ret_n) {
            asm_xtensa_op_ret_n(&emit->as);
        } else {
            goto unknown_op;
        }

    } else if (n_args == 1) {
        if (op == MP_QSTR_callx0) {
            uint r0 = get_arg_reg(emit, op_str, pn_args[0]);
            asm_xtensa_op_callx0(&emit->as, r0);
        } else if (op == MP_QSTR_j) {
            int label = get_arg_label(emit, op_str, pn_args[0]);
            asm_xtensa_j_label(&emit->as, label);
        } else if (op == MP_QSTR_jx) {
            uint r0 = get_arg_reg(emit, op_str, pn_args[0]);
            asm_xtensa_op_jx(&emit->as, r0);
        } else {
            goto unknown_op;
        }

    } else if (n_args == 2) {
        uint r0 = get_arg_reg(emit, op_str, pn_args[0]);
        if (op == MP_QSTR_beqz) {
            int label = get_arg_label(emit, op_str, pn_args[1]);
            asm_xtensa_bccz_reg_label(&emit->as, ASM_XTENSA_CCZ_EQ, r0, label);
        } else if (op == MP_QSTR_bnez) {
            int label = get_arg_label(emit, op_str, pn_args[1]);
            asm_xtensa_bccz_reg_label(&emit->as, ASM_XTENSA_CCZ_NE, r0, label);
        } else if (op == MP_QSTR_mov || op == MP_QSTR_mov_n) {
            // we emit mov.n for both "mov" and "mov_n" opcodes
            uint r1 = get_arg_reg(emit, op_str, pn_args[1]);
            asm_xtensa_op_mov_n(&emit->as, r0, r1);
        } else if (op == MP_QSTR_movi) {
            // for convenience we emit l32r if the integer doesn't fit in movi
            uint32_t imm = get_arg_i(emit, op_str, pn_args[1], 0, 0);
            asm_xtensa_mov_reg_i32(&emit->as, r0, imm);
        } else {
            goto unknown_op;
        }

    } else if (n_args == 3) {
        // search table for 3 arg instructions
        for (uint i = 0; i < MP_ARRAY_SIZE(opcode_table_3arg); i++) {
            const opcode_table_3arg_t *o = &opcode_table_3arg[i];
            if (op == o->name) {
                uint r0 = get_arg_reg(emit, op_str, pn_args[0]);
                uint r1 = get_arg_reg(emit, op_str, pn_args[1]);
                if (o->type == RRR) {
                    uint r2 = get_arg_reg(emit, op_str, pn_args[2]);
                    asm_xtensa_op24(&emit->as, ASM_XTENSA_ENCODE_RRR(0, o->a0, o->a1, r0, r1, r2));
                } else if (o->type == RRI8_B) {
                    int label = get_arg_label(emit, op_str, pn_args[2]);
                    asm_xtensa_bcc_reg_reg_label(&emit->as, o->a0, r0, r1, label);
                } else {
                    int shift, min, max;
                    if ((o->type & 0xf0) == 0) {
                        shift = 0;
                        min = -128;
                        max = 127;
                    } else {
                        shift = (o->type & 0xf0) >> 5;
                        min = 0;
                        max = 0xff << shift;
                    }
                    uint32_t imm = get_arg_i(emit, op_str, pn_args[2], min, max);
                    asm_xtensa_op24(&emit->as, ASM_XTENSA_ENCODE_RRI8(o->a0, o->a1, r1, r0, (imm >> shift) & 0xff));
                }
                return;
            }
        }
        goto unknown_op;

    } else {
STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, int n_args, mp_parse_node_t *pn_args) {
    // TODO perhaps make two tables:
    // one_args =
    // "b", LAB, asm_thumb_b_n,
    // "bgt", LAB, asm_thumb_bgt_n,
    // two_args =
    // "movs", RLO, I8, asm_thumb_movs_reg_i8
    // "movw", REG, REG, asm_thumb_movw_reg_i16
    // three_args =
    // "subs", RLO, RLO, I3, asm_thumb_subs_reg_reg_i3

    // 1 arg
    if (strcmp(qstr_str(op), "b") == 0) {
        if (!check_n_arg(op, n_args, 1)) {
            return;
        }
        int label_num = get_arg_label(emit, op, pn_args, 0);
        // TODO check that this succeeded, ie branch was within range
        asm_thumb_b_n(emit->as, label_num);
    } else if (strcmp(qstr_str(op), "bgt") == 0) {
        if (!check_n_arg(op, n_args, 1)) {
            return;
        }
        int label_num = get_arg_label(emit, op, pn_args, 0);
        // TODO check that this succeeded, ie branch was within range
        asm_thumb_bcc_n(emit->as, THUMB_CC_GT, label_num);

    // 2 args
    } else if (strcmp(qstr_str(op), "movs") == 0) {
        if (!check_n_arg(op, n_args, 2)) {
            return;
        }
        uint rlo_dest = get_arg_rlo(op, pn_args, 0);
        int i_src = get_arg_i(op, pn_args, 1, 0xff);
        asm_thumb_movs_rlo_i8(emit->as, rlo_dest, i_src);
    } else if (strcmp(qstr_str(op), "movw") == 0) {
        if (!check_n_arg(op, n_args, 2)) {
            return;
        }
        uint rlo_dest = get_arg_rlo(op, pn_args, 0); // TODO can be reg lo or hi
        int i_src = get_arg_i(op, pn_args, 1, 0xffff);
        asm_thumb_movw_reg_i16(emit->as, rlo_dest, i_src);
    } else if (strcmp(qstr_str(op), "cmp") == 0) {
        if (!check_n_arg(op, n_args, 2)) {
            return;
        }
        uint rlo = get_arg_rlo(op, pn_args, 0);
        int i8 = get_arg_i(op, pn_args, 1, 0xff);
        asm_thumb_cmp_rlo_i8(emit->as, rlo, i8);

    // 3 args
    } else if (strcmp(qstr_str(op), "subs") == 0) {
        if (!check_n_arg(op, n_args, 3)) {
            return;
        }
        uint rlo_dest = get_arg_rlo(op, pn_args, 0);
        uint rlo_src = get_arg_rlo(op, pn_args, 1);
        int i3_src = get_arg_i(op, pn_args, 2, 0x7);
        asm_thumb_subs_rlo_rlo_i3(emit->as, rlo_dest, rlo_src, i3_src);

    // unknown op
    } else {
        printf("SyntaxError: unsupported ARM Thumb instruction '%s'\n", qstr_str(op));
        return;
    }
}