summaryrefslogblamecommitdiffstats
path: root/target/hppa/translate.c
blob: 5d571f0a4e388b75156389318b733c1188e00dbf (plain) (tree)
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606




















































































                                                                               

                                        





                                                               
      



































































                                                                       







































































                                                                         


























                                                        
                                                         





                             

















                                                                     
















                                                      





















                                                             
















                                                                 








                                      




                                                                 





























































































                                                                              
























                                                                         
                      





                                                

                                                        










                                                                    









                                                                   


















                                                        








                                                                          




























                                            







                                               







                                                                      









                                                                        








                                               









                                               








                                               




































































































                                                                                

















                                                             


































































































































































































































































































































                                                                              



































































































































































































































                                                                             







































































































                                                                         












































                                                                            




















                                                                           
                                                          





                                         
                                                      







                                                                   
                                            

                            
                                               





                                                      
                                

















































































                                                                              

























































                                                                     






                                                             









































































































































































































                                                                                






































































































































































































































































































































































                                                                                














































































































































                                                                     














































                                                                         

























































































































































                                                                             




























































































































                                                                           
















































































































































































































































































































                                                                       




























































































                                                                            



































































































































































































































































































































































































































































































































































                                                                              



















                                                                        



                                                          

                                                           

                                                           

                                               

                                     

                                       

                                      

                                        

                                                          

                                    

                                                          

























                                                      







                                                          



                                        

                                              









                                                        


                                     

                                                          

                                                         
 






                                            



                                                       





                                                        










                                     








































                                                                          








                                                                            











                                                                    



                                            














                                                                         






                                                               
















                                                                         







                                                                      











                                                                    
                               


















                                                            
                           




















                                             


















                                                                         
















                                                                       
/*
 * HPPA emulation cpu translation for qemu.
 *
 * Copyright (c) 2016 Richard Henderson <rth@twiddle.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */

#include "qemu/osdep.h"
#include "cpu.h"
#include "disas/disas.h"
#include "qemu/host-utils.h"
#include "exec/exec-all.h"
#include "tcg-op.h"
#include "exec/cpu_ldst.h"

#include "exec/helper-proto.h"
#include "exec/helper-gen.h"

#include "trace-tcg.h"
#include "exec/log.h"

typedef struct DisasCond {
    TCGCond c;
    TCGv a0, a1;
    bool a0_is_n;
    bool a1_is_0;
} DisasCond;

typedef struct DisasContext {
    struct TranslationBlock *tb;
    CPUState *cs;

    target_ulong iaoq_f;
    target_ulong iaoq_b;
    target_ulong iaoq_n;
    TCGv iaoq_n_var;

    int ntemps;
    TCGv temps[8];

    DisasCond null_cond;
    TCGLabel *null_lab;

    bool singlestep_enabled;
    bool psw_n_nonzero;
} DisasContext;

/* Return values from translate_one, indicating the state of the TB.
   Note that zero indicates that we are not exiting the TB.  */

typedef enum {
    NO_EXIT,

    /* We have emitted one or more goto_tb.  No fixup required.  */
    EXIT_GOTO_TB,

    /* We are not using a goto_tb (for whatever reason), but have updated
       the iaq (for whatever reason), so don't do it again on exit.  */
    EXIT_IAQ_N_UPDATED,

    /* We are exiting the TB, but have neither emitted a goto_tb, nor
       updated the iaq for the next instruction to be executed.  */
    EXIT_IAQ_N_STALE,

    /* We are ending the TB with a noreturn function call, e.g. longjmp.
       No following code will be executed.  */
    EXIT_NORETURN,
} ExitStatus;

typedef struct DisasInsn {
    uint32_t insn, mask;
    ExitStatus (*trans)(DisasContext *ctx, uint32_t insn,
                        const struct DisasInsn *f);
    union {
        void (*f_ttt)(TCGv, TCGv, TCGv);
        void (*f_weww)(TCGv_i32, TCGv_env, TCGv_i32, TCGv_i32);
        void (*f_dedd)(TCGv_i64, TCGv_env, TCGv_i64, TCGv_i64);
        void (*f_wew)(TCGv_i32, TCGv_env, TCGv_i32);
        void (*f_ded)(TCGv_i64, TCGv_env, TCGv_i64);
        void (*f_wed)(TCGv_i32, TCGv_env, TCGv_i64);
        void (*f_dew)(TCGv_i64, TCGv_env, TCGv_i32);
    };
} DisasInsn;

/* global register indexes */
static TCGv_env cpu_env;
static TCGv cpu_gr[32];
static TCGv cpu_iaoq_f;
static TCGv cpu_iaoq_b;
static TCGv cpu_sar;
static TCGv cpu_psw_n;
static TCGv cpu_psw_v;
static TCGv cpu_psw_cb;
static TCGv cpu_psw_cb_msb;
static TCGv cpu_cr26;
static TCGv cpu_cr27;

#include "exec/gen-icount.h"

void hppa_translate_init(void)
{
#define DEF_VAR(V)  { &cpu_##V, #V, offsetof(CPUHPPAState, V) }

    typedef struct { TCGv *var; const char *name; int ofs; } GlobalVar;
    static const GlobalVar vars[] = {
        DEF_VAR(sar),
        DEF_VAR(cr26),
        DEF_VAR(cr27),
        DEF_VAR(psw_n),
        DEF_VAR(psw_v),
        DEF_VAR(psw_cb),
        DEF_VAR(psw_cb_msb),
        DEF_VAR(iaoq_f),
        DEF_VAR(iaoq_b),
    };

#undef DEF_VAR

    /* Use the symbolic register names that match the disassembler.  */
    static const char gr_names[32][4] = {
        "r0",  "r1",  "r2",  "r3",  "r4",  "r5",  "r6",  "r7",
        "r8",  "r9",  "r10", "r11", "r12", "r13", "r14", "r15",
        "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
        "r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31"
    };

    static bool done_init = 0;
    int i;

    if (done_init) {
        return;
    }
    done_init = 1;

    cpu_env = tcg_global_reg_new_ptr(TCG_AREG0, "env");
    tcg_ctx.tcg_env = cpu_env;

    TCGV_UNUSED(cpu_gr[0]);
    for (i = 1; i < 32; i++) {
        cpu_gr[i] = tcg_global_mem_new(cpu_env,
                                       offsetof(CPUHPPAState, gr[i]),
                                       gr_names[i]);
    }

    for (i = 0; i < ARRAY_SIZE(vars); ++i) {
        const GlobalVar *v = &vars[i];
        *v->var = tcg_global_mem_new(cpu_env, v->ofs, v->name);
    }
}

static DisasCond cond_make_f(void)
{
    DisasCond r = { .c = TCG_COND_NEVER };
    TCGV_UNUSED(r.a0);
    TCGV_UNUSED(r.a1);
    return r;
}

static DisasCond cond_make_n(void)
{
    DisasCond r = { .c = TCG_COND_NE, .a0_is_n = true, .a1_is_0 = true };
    r.a0 = cpu_psw_n;
    TCGV_UNUSED(r.a1);
    return r;
}

static DisasCond cond_make_0(TCGCond c, TCGv a0)
{
    DisasCond r = { .c = c, .a1_is_0 = true };

    assert (c != TCG_COND_NEVER && c != TCG_COND_ALWAYS);
    r.a0 = tcg_temp_new();
    tcg_gen_mov_tl(r.a0, a0);
    TCGV_UNUSED(r.a1);

    return r;
}

static DisasCond cond_make(TCGCond c, TCGv a0, TCGv a1)
{
    DisasCond r = { .c = c };

    assert (c != TCG_COND_NEVER && c != TCG_COND_ALWAYS);
    r.a0 = tcg_temp_new();
    tcg_gen_mov_tl(r.a0, a0);
    r.a1 = tcg_temp_new();
    tcg_gen_mov_tl(r.a1, a1);

    return r;
}

static void cond_prep(DisasCond *cond)
{
    if (cond->a1_is_0) {
        cond->a1_is_0 = false;
        cond->a1 = tcg_const_tl(0);
    }
}

static void cond_free(DisasCond *cond)
{
    switch (cond->c) {
    default:
        if (!cond->a0_is_n) {
            tcg_temp_free(cond->a0);
        }
        if (!cond->a1_is_0) {
            tcg_temp_free(cond->a1);
        }
        cond->a0_is_n = false;
        cond->a1_is_0 = false;
        TCGV_UNUSED(cond->a0);
        TCGV_UNUSED(cond->a1);
        /* fallthru */
    case TCG_COND_ALWAYS:
        cond->c = TCG_COND_NEVER;
        break;
    case TCG_COND_NEVER:
        break;
    }
}

static TCGv get_temp(DisasContext *ctx)
{
    unsigned i = ctx->ntemps++;
    g_assert(i < ARRAY_SIZE(ctx->temps));
    return ctx->temps[i] = tcg_temp_new();
}

static TCGv load_const(DisasContext *ctx, target_long v)
{
    TCGv t = get_temp(ctx);
    tcg_gen_movi_tl(t, v);
    return t;
}

static TCGv load_gpr(DisasContext *ctx, unsigned reg)
{
    if (reg == 0) {
        TCGv t = get_temp(ctx);
        tcg_gen_movi_tl(t, 0);
        return t;
    } else {
        return cpu_gr[reg];
    }
}

static TCGv dest_gpr(DisasContext *ctx, unsigned reg)
{
    if (reg == 0 || ctx->null_cond.c != TCG_COND_NEVER) {
        return get_temp(ctx);
    } else {
        return cpu_gr[reg];
    }
}

static void save_or_nullify(DisasContext *ctx, TCGv dest, TCGv t)
{
    if (ctx->null_cond.c != TCG_COND_NEVER) {
        cond_prep(&ctx->null_cond);
        tcg_gen_movcond_tl(ctx->null_cond.c, dest, ctx->null_cond.a0,
                           ctx->null_cond.a1, dest, t);
    } else {
        tcg_gen_mov_tl(dest, t);
    }
}

static void save_gpr(DisasContext *ctx, unsigned reg, TCGv t)
{
    if (reg != 0) {
        save_or_nullify(ctx, cpu_gr[reg], t);
    }
}

#ifdef HOST_WORDS_BIGENDIAN
# define HI_OFS  0
# define LO_OFS  4
#else
# define HI_OFS  4
# define LO_OFS  0
#endif

static TCGv_i32 load_frw_i32(unsigned rt)
{
    TCGv_i32 ret = tcg_temp_new_i32();
    tcg_gen_ld_i32(ret, cpu_env,
                   offsetof(CPUHPPAState, fr[rt & 31])
                   + (rt & 32 ? LO_OFS : HI_OFS));
    return ret;
}

static TCGv_i32 load_frw0_i32(unsigned rt)
{
    if (rt == 0) {
        return tcg_const_i32(0);
    } else {
        return load_frw_i32(rt);
    }
}

static TCGv_i64 load_frw0_i64(unsigned rt)
{
    if (rt == 0) {
        return tcg_const_i64(0);
    } else {
        TCGv_i64 ret = tcg_temp_new_i64();
        tcg_gen_ld32u_i64(ret, cpu_env,
                          offsetof(CPUHPPAState, fr[rt & 31])
                          + (rt & 32 ? LO_OFS : HI_OFS));
        return ret;
    }
}

static void save_frw_i32(unsigned rt, TCGv_i32 val)
{
    tcg_gen_st_i32(val, cpu_env,
                   offsetof(CPUHPPAState, fr[rt & 31])
                   + (rt & 32 ? LO_OFS : HI_OFS));
}

#undef HI_OFS
#undef LO_OFS

static TCGv_i64 load_frd(unsigned rt)
{
    TCGv_i64 ret = tcg_temp_new_i64();
    tcg_gen_ld_i64(ret, cpu_env, offsetof(CPUHPPAState, fr[rt]));
    return ret;
}

static TCGv_i64 load_frd0(unsigned rt)
{
    if (rt == 0) {
        return tcg_const_i64(0);
    } else {
        return load_frd(rt);
    }
}

static void save_frd(unsigned rt, TCGv_i64 val)
{
    tcg_gen_st_i64(val, cpu_env, offsetof(CPUHPPAState, fr[rt]));
}

/* Skip over the implementation of an insn that has been nullified.
   Use this when the insn is too complex for a conditional move.  */
static void nullify_over(DisasContext *ctx)
{
    if (ctx->null_cond.c != TCG_COND_NEVER) {
        /* The always condition should have been handled in the main loop.  */
        assert(ctx->null_cond.c != TCG_COND_ALWAYS);

        ctx->null_lab = gen_new_label();
        cond_prep(&ctx->null_cond);

        /* If we're using PSW[N], copy it to a temp because... */
        if (ctx->null_cond.a0_is_n) {
            ctx->null_cond.a0_is_n = false;
            ctx->null_cond.a0 = tcg_temp_new();
            tcg_gen_mov_tl(ctx->null_cond.a0, cpu_psw_n);
        }
        /* ... we clear it before branching over the implementation,
           so that (1) it's clear after nullifying this insn and
           (2) if this insn nullifies the next, PSW[N] is valid.  */
        if (ctx->psw_n_nonzero) {
            ctx->psw_n_nonzero = false;
            tcg_gen_movi_tl(cpu_psw_n, 0);
        }

        tcg_gen_brcond_tl(ctx->null_cond.c, ctx->null_cond.a0,
                          ctx->null_cond.a1, ctx->null_lab);
        cond_free(&ctx->null_cond);
    }
}

/* Save the current nullification state to PSW[N].  */
static void nullify_save(DisasContext *ctx)
{
    if (ctx->null_cond.c == TCG_COND_NEVER) {
        if (ctx->psw_n_nonzero) {
            tcg_gen_movi_tl(cpu_psw_n, 0);
        }
        return;
    }
    if (!ctx->null_cond.a0_is_n) {
        cond_prep(&ctx->null_cond);
        tcg_gen_setcond_tl(ctx->null_cond.c, cpu_psw_n,
                           ctx->null_cond.a0, ctx->null_cond.a1);
        ctx->psw_n_nonzero = true;
    }
    cond_free(&ctx->null_cond);
}

/* Set a PSW[N] to X.  The intention is that this is used immediately
   before a goto_tb/exit_tb, so that there is no fallthru path to other
   code within the TB.  Therefore we do not update psw_n_nonzero.  */
static void nullify_set(DisasContext *ctx, bool x)
{
    if (ctx->psw_n_nonzero || x) {
        tcg_gen_movi_tl(cpu_psw_n, x);
    }
}

/* Mark the end of an instruction that may have been nullified.
   This is the pair to nullify_over.  */
static ExitStatus nullify_end(DisasContext *ctx, ExitStatus status)
{
    TCGLabel *null_lab = ctx->null_lab;

    if (likely(null_lab == NULL)) {
        /* The current insn wasn't conditional or handled the condition
           applied to it without a branch, so the (new) setting of
           NULL_COND can be applied directly to the next insn.  */
        return status;
    }
    ctx->null_lab = NULL;

    if (likely(ctx->null_cond.c == TCG_COND_NEVER)) {
        /* The next instruction will be unconditional,
           and NULL_COND already reflects that.  */
        gen_set_label(null_lab);
    } else {
        /* The insn that we just executed is itself nullifying the next
           instruction.  Store the condition in the PSW[N] global.
           We asserted PSW[N] = 0 in nullify_over, so that after the
           label we have the proper value in place.  */
        nullify_save(ctx);
        gen_set_label(null_lab);
        ctx->null_cond = cond_make_n();
    }

    assert(status != EXIT_GOTO_TB && status != EXIT_IAQ_N_UPDATED);
    if (status == EXIT_NORETURN) {
        status = NO_EXIT;
    }
    return status;
}

static void copy_iaoq_entry(TCGv dest, target_ulong ival, TCGv vval)
{
    if (unlikely(ival == -1)) {
        tcg_gen_mov_tl(dest, vval);
    } else {
        tcg_gen_movi_tl(dest, ival);
    }
}

static inline target_ulong iaoq_dest(DisasContext *ctx, target_long disp)
{
    return ctx->iaoq_f + disp + 8;
}

static void gen_excp_1(int exception)
{
    TCGv_i32 t = tcg_const_i32(exception);
    gen_helper_excp(cpu_env, t);
    tcg_temp_free_i32(t);
}

static ExitStatus gen_excp(DisasContext *ctx, int exception)
{
    copy_iaoq_entry(cpu_iaoq_f, ctx->iaoq_f, cpu_iaoq_f);
    copy_iaoq_entry(cpu_iaoq_b, ctx->iaoq_b, cpu_iaoq_b);
    nullify_save(ctx);
    gen_excp_1(exception);
    return EXIT_NORETURN;
}

static ExitStatus gen_illegal(DisasContext *ctx)
{
    nullify_over(ctx);
    return nullify_end(ctx, gen_excp(ctx, EXCP_SIGILL));
}

static bool use_goto_tb(DisasContext *ctx, target_ulong dest)
{
    /* Suppress goto_tb in the case of single-steping and IO.  */
    if ((ctx->tb->cflags & CF_LAST_IO) || ctx->singlestep_enabled) {
        return false;
    }
    return true;
}

/* If the next insn is to be nullified, and it's on the same page,
   and we're not attempting to set a breakpoint on it, then we can
   totally skip the nullified insn.  This avoids creating and
   executing a TB that merely branches to the next TB.  */
static bool use_nullify_skip(DisasContext *ctx)
{
    return (((ctx->iaoq_b ^ ctx->iaoq_f) & TARGET_PAGE_MASK) == 0
            && !cpu_breakpoint_test(ctx->cs, ctx->iaoq_b, BP_ANY));
}

static void gen_goto_tb(DisasContext *ctx, int which,
                        target_ulong f, target_ulong b)
{
    if (f != -1 && b != -1 && use_goto_tb(ctx, f)) {
        tcg_gen_goto_tb(which);
        tcg_gen_movi_tl(cpu_iaoq_f, f);
        tcg_gen_movi_tl(cpu_iaoq_b, b);
        tcg_gen_exit_tb((uintptr_t)ctx->tb + which);
    } else {
        copy_iaoq_entry(cpu_iaoq_f, f, cpu_iaoq_b);
        copy_iaoq_entry(cpu_iaoq_b, b, ctx->iaoq_n_var);
        if (ctx->singlestep_enabled) {
            gen_excp_1(EXCP_DEBUG);
        } else {
            tcg_gen_exit_tb(0);
        }
    }
}

/* PA has a habit of taking the LSB of a field and using that as the sign,
   with the rest of the field becoming the least significant bits.  */
static target_long low_sextract(uint32_t val, int pos, int len)
{
    target_ulong x = -(target_ulong)extract32(val, pos, 1);
    x = (x << (len - 1)) | extract32(val, pos + 1, len - 1);
    return x;
}

static unsigned assemble_rt64(uint32_t insn)
{
    unsigned r1 = extract32(insn, 6, 1);
    unsigned r0 = extract32(insn, 0, 5);
    return r1 * 32 + r0;
}

static unsigned assemble_ra64(uint32_t insn)
{
    unsigned r1 = extract32(insn, 7, 1);
    unsigned r0 = extract32(insn, 21, 5);
    return r1 * 32 + r0;
}

static unsigned assemble_rb64(uint32_t insn)
{
    unsigned r1 = extract32(insn, 12, 1);
    unsigned r0 = extract32(insn, 16, 5);
    return r1 * 32 + r0;
}

static unsigned assemble_rc64(uint32_t insn)
{
    unsigned r2 = extract32(insn, 8, 1);
    unsigned r1 = extract32(insn, 13, 3);
    unsigned r0 = extract32(insn, 9, 2);
    return r2 * 32 + r1 * 4 + r0;
}

static target_long assemble_12(uint32_t insn)
{
    target_ulong x = -(target_ulong)(insn & 1);
    x = (x <<  1) | extract32(insn, 2, 1);
    x = (x << 10) | extract32(insn, 3, 10);
    return x;
}

static target_long assemble_16(uint32_t insn)
{
    /* Take the name from PA2.0, which produces a 16-bit number
       only with wide mode; otherwise a 14-bit number.  Since we don't
       implement wide mode, this is always the 14-bit number.  */
    return low_sextract(insn, 0, 14);
}

static target_long assemble_16a(uint32_t insn)
{
    /* Take the name from PA2.0, which produces a 14-bit shifted number
       only with wide mode; otherwise a 12-bit shifted number.  Since we
       don't implement wide mode, this is always the 12-bit number.  */
    target_ulong x = -(target_ulong)(insn & 1);
    x = (x << 11) | extract32(insn, 2, 11);
    return x << 2;
}

static target_long assemble_17(uint32_t insn)
{
    target_ulong x = -(target_ulong)(insn & 1);
    x = (x <<  5) | extract32(insn, 16, 5);
    x = (x <<  1) | extract32(insn, 2, 1);
    x = (x << 10) | extract32(insn, 3, 10);
    return x << 2;
}

static target_long assemble_21(uint32_t insn)
{
    target_ulong x = -(target_ulong)(insn & 1);
    x = (x << 11) | extract32(insn, 1, 11);
    x = (x <<  2) | extract32(insn, 14, 2);
    x = (x <<  5) | extract32(insn, 16, 5);
    x = (x <<  2) | extract32(insn, 12, 2);
    return x << 11;
}

static target_long assemble_22(uint32_t insn)
{
    target_ulong x = -(target_ulong)(insn & 1);
    x = (x << 10) | extract32(insn, 16, 10);
    x = (x <<  1) | extract32(insn, 2, 1);
    x = (x << 10) | extract32(insn, 3, 10);
    return x << 2;
}

/* The parisc documentation describes only the general interpretation of
   the conditions, without describing their exact implementation.  The
   interpretations do not stand up well when considering ADD,C and SUB,B.
   However, considering the Addition, Subtraction and Logical conditions
   as a whole it would appear that these relations are similar to what
   a traditional NZCV set of flags would produce.  */

static DisasCond do_cond(unsigned cf, TCGv res, TCGv cb_msb, TCGv sv)
{
    DisasCond cond;
    TCGv tmp;

    switch (cf >> 1) {
    case 0: /* Never / TR */
        cond = cond_make_f();
        break;
    case 1: /* = / <>        (Z / !Z) */
        cond = cond_make_0(TCG_COND_EQ, res);
        break;
    case 2: /* < / >=        (N / !N) */
        cond = cond_make_0(TCG_COND_LT, res);
        break;
    case 3: /* <= / >        (N | Z / !N & !Z) */
        cond = cond_make_0(TCG_COND_LE, res);
        break;
    case 4: /* NUV / UV      (!C / C) */
        cond = cond_make_0(TCG_COND_EQ, cb_msb);
        break;
    case 5: /* ZNV / VNZ     (!C | Z / C & !Z) */
        tmp = tcg_temp_new();
        tcg_gen_neg_tl(tmp, cb_msb);
        tcg_gen_and_tl(tmp, tmp, res);
        cond = cond_make_0(TCG_COND_EQ, tmp);
        tcg_temp_free(tmp);
        break;
    case 6: /* SV / NSV      (V / !V) */
        cond = cond_make_0(TCG_COND_LT, sv);
        break;
    case 7: /* OD / EV */
        tmp = tcg_temp_new();
        tcg_gen_andi_tl(tmp, res, 1);
        cond = cond_make_0(TCG_COND_NE, tmp);
        tcg_temp_free(tmp);
        break;
    default:
        g_assert_not_reached();
    }
    if (cf & 1) {
        cond.c = tcg_invert_cond(cond.c);
    }

    return cond;
}

/* Similar, but for the special case of subtraction without borrow, we
   can use the inputs directly.  This can allow other computation to be
   deleted as unused.  */

static DisasCond do_sub_cond(unsigned cf, TCGv res, TCGv in1, TCGv in2, TCGv sv)
{
    DisasCond cond;

    switch (cf >> 1) {
    case 1: /* = / <> */
        cond = cond_make(TCG_COND_EQ, in1, in2);
        break;
    case 2: /* < / >= */
        cond = cond_make(TCG_COND_LT, in1, in2);
        break;
    case 3: /* <= / > */
        cond = cond_make(TCG_COND_LE, in1, in2);
        break;
    case 4: /* << / >>= */
        cond = cond_make(TCG_COND_LTU, in1, in2);
        break;
    case 5: /* <<= / >> */
        cond = cond_make(TCG_COND_LEU, in1, in2);
        break;
    default:
        return do_cond(cf, res, sv, sv);
    }
    if (cf & 1) {
        cond.c = tcg_invert_cond(cond.c);
    }

    return cond;
}

/* Similar, but for logicals, where the carry and overflow bits are not
   computed, and use of them is undefined.  */

static DisasCond do_log_cond(unsigned cf, TCGv res)
{
    switch (cf >> 1) {
    case 4: case 5: case 6:
        cf &= 1;
        break;
    }
    return do_cond(cf, res, res, res);
}

/* Similar, but for shift/extract/deposit conditions.  */

static DisasCond do_sed_cond(unsigned orig, TCGv res)
{
    unsigned c, f;

    /* Convert the compressed condition codes to standard.
       0-2 are the same as logicals (nv,<,<=), while 3 is OD.
       4-7 are the reverse of 0-3.  */
    c = orig & 3;
    if (c == 3) {
        c = 7;
    }
    f = (orig & 4) / 4;

    return do_log_cond(c * 2 + f, res);
}

/* Similar, but for unit conditions.  */

static DisasCond do_unit_cond(unsigned cf, TCGv res, TCGv in1, TCGv in2)
{
    DisasCond cond;
    TCGv tmp, cb;

    TCGV_UNUSED(cb);
    if (cf & 8) {
        /* Since we want to test lots of carry-out bits all at once, do not
         * do our normal thing and compute carry-in of bit B+1 since that
         * leaves us with carry bits spread across two words.
         */
        cb = tcg_temp_new();
        tmp = tcg_temp_new();
        tcg_gen_or_tl(cb, in1, in2);
        tcg_gen_and_tl(tmp, in1, in2);
        tcg_gen_andc_tl(cb, cb, res);
        tcg_gen_or_tl(cb, cb, tmp);
        tcg_temp_free(tmp);
    }

    switch (cf >> 1) {
    case 0: /* never / TR */
    case 1: /* undefined */
    case 5: /* undefined */
        cond = cond_make_f();
        break;

    case 2: /* SBZ / NBZ */
        /* See hasless(v,1) from
         * https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord
         */
        tmp = tcg_temp_new();
        tcg_gen_subi_tl(tmp, res, 0x01010101u);
        tcg_gen_andc_tl(tmp, tmp, res);
        tcg_gen_andi_tl(tmp, tmp, 0x80808080u);
        cond = cond_make_0(TCG_COND_NE, tmp);
        tcg_temp_free(tmp);
        break;

    case 3: /* SHZ / NHZ */
        tmp = tcg_temp_new();
        tcg_gen_subi_tl(tmp, res, 0x00010001u);
        tcg_gen_andc_tl(tmp, tmp, res);
        tcg_gen_andi_tl(tmp, tmp, 0x80008000u);
        cond = cond_make_0(TCG_COND_NE, tmp);
        tcg_temp_free(tmp);
        break;

    case 4: /* SDC / NDC */
        tcg_gen_andi_tl(cb, cb, 0x88888888u);
        cond = cond_make_0(TCG_COND_NE, cb);
        break;

    case 6: /* SBC / NBC */
        tcg_gen_andi_tl(cb, cb, 0x80808080u);
        cond = cond_make_0(TCG_COND_NE, cb);
        break;

    case 7: /* SHC / NHC */
        tcg_gen_andi_tl(cb, cb, 0x80008000u);
        cond = cond_make_0(TCG_COND_NE, cb);
        break;

    default:
        g_assert_not_reached();
    }
    if (cf & 8) {
        tcg_temp_free(cb);
    }
    if (cf & 1) {
        cond.c = tcg_invert_cond(cond.c);
    }

    return cond;
}

/* Compute signed overflow for addition.  */
static TCGv do_add_sv(DisasContext *ctx, TCGv res, TCGv in1, TCGv in2)
{
    TCGv sv = get_temp(ctx);
    TCGv tmp = tcg_temp_new();

    tcg_gen_xor_tl(sv, res, in1);
    tcg_gen_xor_tl(tmp, in1, in2);
    tcg_gen_andc_tl(sv, sv, tmp);
    tcg_temp_free(tmp);

    return sv;
}

/* Compute signed overflow for subtraction.  */
static TCGv do_sub_sv(DisasContext *ctx, TCGv res, TCGv in1, TCGv in2)
{
    TCGv sv = get_temp(ctx);
    TCGv tmp = tcg_temp_new();

    tcg_gen_xor_tl(sv, res, in1);
    tcg_gen_xor_tl(tmp, in1, in2);
    tcg_gen_and_tl(sv, sv, tmp);
    tcg_temp_free(tmp);

    return sv;
}

static ExitStatus do_add(DisasContext *ctx, unsigned rt, TCGv in1, TCGv in2,
                         unsigned shift, bool is_l, bool is_tsv, bool is_tc,
                         bool is_c, unsigned cf)
{
    TCGv dest, cb, cb_msb, sv, tmp;
    unsigned c = cf >> 1;
    DisasCond cond;

    dest = tcg_temp_new();
    TCGV_UNUSED(cb);
    TCGV_UNUSED(cb_msb);

    if (shift) {
        tmp = get_temp(ctx);
        tcg_gen_shli_tl(tmp, in1, shift);
        in1 = tmp;
    }

    if (!is_l || c == 4 || c == 5) {
        TCGv zero = tcg_const_tl(0);
        cb_msb = get_temp(ctx);
        tcg_gen_add2_tl(dest, cb_msb, in1, zero, in2, zero);
        if (is_c) {
            tcg_gen_add2_tl(dest, cb_msb, dest, cb_msb, cpu_psw_cb_msb, zero);
        }
        tcg_temp_free(zero);
        if (!is_l) {
            cb = get_temp(ctx);
            tcg_gen_xor_tl(cb, in1, in2);
            tcg_gen_xor_tl(cb, cb, dest);
        }
    } else {
        tcg_gen_add_tl(dest, in1, in2);
        if (is_c) {
            tcg_gen_add_tl(dest, dest, cpu_psw_cb_msb);
        }
    }

    /* Compute signed overflow if required.  */
    TCGV_UNUSED(sv);
    if (is_tsv || c == 6) {
        sv = do_add_sv(ctx, dest, in1, in2);
        if (is_tsv) {
            /* ??? Need to include overflow from shift.  */
            gen_helper_tsv(cpu_env, sv);
        }
    }

    /* Emit any conditional trap before any writeback.  */
    cond = do_cond(cf, dest, cb_msb, sv);
    if (is_tc) {
        cond_prep(&cond);
        tmp = tcg_temp_new();
        tcg_gen_setcond_tl(cond.c, tmp, cond.a0, cond.a1);
        gen_helper_tcond(cpu_env, tmp);
        tcg_temp_free(tmp);
    }

    /* Write back the result.  */
    if (!is_l) {
        save_or_nullify(ctx, cpu_psw_cb, cb);
        save_or_nullify(ctx, cpu_psw_cb_msb, cb_msb);
    }
    save_gpr(ctx, rt, dest);
    tcg_temp_free(dest);

    /* Install the new nullification.  */
    cond_free(&ctx->null_cond);
    ctx->null_cond = cond;
    return NO_EXIT;
}

static ExitStatus do_sub(DisasContext *ctx, unsigned rt, TCGv in1, TCGv in2,
                         bool is_tsv, bool is_b, bool is_tc, unsigned cf)
{
    TCGv dest, sv, cb, cb_msb, zero, tmp;
    unsigned c = cf >> 1;
    DisasCond cond;

    dest = tcg_temp_new();
    cb = tcg_temp_new();
    cb_msb = tcg_temp_new();

    zero = tcg_const_tl(0);
    if (is_b) {
        /* DEST,C = IN1 + ~IN2 + C.  */
        tcg_gen_not_tl(cb, in2);
        tcg_gen_add2_tl(dest, cb_msb, in1, zero, cpu_psw_cb_msb, zero);
        tcg_gen_add2_tl(dest, cb_msb, dest, cb_msb, cb, zero);
        tcg_gen_xor_tl(cb, cb, in1);
        tcg_gen_xor_tl(cb, cb, dest);
    } else {
        /* DEST,C = IN1 + ~IN2 + 1.  We can produce the same result in fewer
           operations by seeding the high word with 1 and subtracting.  */
        tcg_gen_movi_tl(cb_msb, 1);
        tcg_gen_sub2_tl(dest, cb_msb, in1, cb_msb, in2, zero);
        tcg_gen_eqv_tl(cb, in1, in2);
        tcg_gen_xor_tl(cb, cb, dest);
    }
    tcg_temp_free(zero);

    /* Compute signed overflow if required.  */
    TCGV_UNUSED(sv);
    if (is_tsv || c == 6) {
        sv = do_sub_sv(ctx, dest, in1, in2);
        if (is_tsv) {
            gen_helper_tsv(cpu_env, sv);
        }
    }

    /* Compute the condition.  We cannot use the special case for borrow.  */
    if (!is_b) {
        cond = do_sub_cond(cf, dest, in1, in2, sv);
    } else {
        cond = do_cond(cf, dest, cb_msb, sv);
    }

    /* Emit any conditional trap before any writeback.  */
    if (is_tc) {
        cond_prep(&cond);
        tmp = tcg_temp_new();
        tcg_gen_setcond_tl(cond.c, tmp, cond.a0, cond.a1);
        gen_helper_tcond(cpu_env, tmp);
        tcg_temp_free(tmp);
    }

    /* Write back the result.  */
    save_or_nullify(ctx, cpu_psw_cb, cb);
    save_or_nullify(ctx, cpu_psw_cb_msb, cb_msb);
    save_gpr(ctx, rt, dest);
    tcg_temp_free(dest);

    /* Install the new nullification.  */
    cond_free(&ctx->null_cond);
    ctx->null_cond = cond;
    return NO_EXIT;
}

static ExitStatus do_cmpclr(DisasContext *ctx, unsigned rt, TCGv in1,
                            TCGv in2, unsigned cf)
{
    TCGv dest, sv;
    DisasCond cond;

    dest = tcg_temp_new();
    tcg_gen_sub_tl(dest, in1, in2);

    /* Compute signed overflow if required.  */
    TCGV_UNUSED(sv);
    if ((cf >> 1) == 6) {
        sv = do_sub_sv(ctx, dest, in1, in2);
    }

    /* Form the condition for the compare.  */
    cond = do_sub_cond(cf, dest, in1, in2, sv);

    /* Clear.  */
    tcg_gen_movi_tl(dest, 0);
    save_gpr(ctx, rt, dest);
    tcg_temp_free(dest);

    /* Install the new nullification.  */
    cond_free(&ctx->null_cond);
    ctx->null_cond = cond;
    return NO_EXIT;
}

static ExitStatus do_log(DisasContext *ctx, unsigned rt, TCGv in1, TCGv in2,
                         unsigned cf, void (*fn)(TCGv, TCGv, TCGv))
{
    TCGv dest = dest_gpr(ctx, rt);

    /* Perform the operation, and writeback.  */
    fn(dest, in1, in2);
    save_gpr(ctx, rt, dest);

    /* Install the new nullification.  */
    cond_free(&ctx->null_cond);
    if (cf) {
        ctx->null_cond = do_log_cond(cf, dest);
    }
    return NO_EXIT;
}

static ExitStatus do_unit(DisasContext *ctx, unsigned rt, TCGv in1,
                          TCGv in2, unsigned cf, bool is_tc,
                          void (*fn)(TCGv, TCGv, TCGv))
{
    TCGv dest;
    DisasCond cond;

    if (cf == 0) {
        dest = dest_gpr(ctx, rt);
        fn(dest, in1, in2);
        save_gpr(ctx, rt, dest);
        cond_free(&ctx->null_cond);
    } else {
        dest = tcg_temp_new();
        fn(dest, in1, in2);

        cond = do_unit_cond(cf, dest, in1, in2);

        if (is_tc) {
            TCGv tmp = tcg_temp_new();
            cond_prep(&cond);
            tcg_gen_setcond_tl(cond.c, tmp, cond.a0, cond.a1);
            gen_helper_tcond(cpu_env, tmp);
            tcg_temp_free(tmp);
        }
        save_gpr(ctx, rt, dest);

        cond_free(&ctx->null_cond);
        ctx->null_cond = cond;
    }
    return NO_EXIT;
}

/* Emit a memory load.  The modify parameter should be
 * < 0 for pre-modify,
 * > 0 for post-modify,
 * = 0 for no base register update.
 */
static void do_load_32(DisasContext *ctx, TCGv_i32 dest, unsigned rb,
                       unsigned rx, int scale, target_long disp,
                       int modify, TCGMemOp mop)
{
    TCGv addr, base;

    /* Caller uses nullify_over/nullify_end.  */
    assert(ctx->null_cond.c == TCG_COND_NEVER);

    addr = tcg_temp_new();
    base = load_gpr(ctx, rb);

    /* Note that RX is mutually exclusive with DISP.  */
    if (rx) {
        tcg_gen_shli_tl(addr, cpu_gr[rx], scale);
        tcg_gen_add_tl(addr, addr, base);
    } else {
        tcg_gen_addi_tl(addr, base, disp);
    }

    if (modify == 0) {
        tcg_gen_qemu_ld_i32(dest, addr, MMU_USER_IDX, mop);
    } else {
        tcg_gen_qemu_ld_i32(dest, (modify < 0 ? addr : base),
                            MMU_USER_IDX, mop);
        save_gpr(ctx, rb, addr);
    }
    tcg_temp_free(addr);
}

static void do_load_64(DisasContext *ctx, TCGv_i64 dest, unsigned rb,
                       unsigned rx, int scale, target_long disp,
                       int modify, TCGMemOp mop)
{
    TCGv addr, base;

    /* Caller uses nullify_over/nullify_end.  */
    assert(ctx->null_cond.c == TCG_COND_NEVER);

    addr = tcg_temp_new();
    base = load_gpr(ctx, rb);

    /* Note that RX is mutually exclusive with DISP.  */
    if (rx) {
        tcg_gen_shli_tl(addr, cpu_gr[rx], scale);
        tcg_gen_add_tl(addr, addr, base);
    } else {
        tcg_gen_addi_tl(addr, base, disp);
    }

    if (modify == 0) {
        tcg_gen_qemu_ld_i64(dest, addr, MMU_USER_IDX, mop);
    } else {
        tcg_gen_qemu_ld_i64(dest, (modify < 0 ? addr : base),
                            MMU_USER_IDX, mop);
        save_gpr(ctx, rb, addr);
    }
    tcg_temp_free(addr);
}

static void do_store_32(DisasContext *ctx, TCGv_i32 src, unsigned rb,
                        unsigned rx, int scale, target_long disp,
                        int modify, TCGMemOp mop)
{
    TCGv addr, base;

    /* Caller uses nullify_over/nullify_end.  */
    assert(ctx->null_cond.c == TCG_COND_NEVER);

    addr = tcg_temp_new();
    base = load_gpr(ctx, rb);

    /* Note that RX is mutually exclusive with DISP.  */
    if (rx) {
        tcg_gen_shli_tl(addr, cpu_gr[rx], scale);
        tcg_gen_add_tl(addr, addr, base);
    } else {
        tcg_gen_addi_tl(addr, base, disp);
    }

    tcg_gen_qemu_st_i32(src, (modify <= 0 ? addr : base), MMU_USER_IDX, mop);

    if (modify != 0) {
        save_gpr(ctx, rb, addr);
    }
    tcg_temp_free(addr);
}

static void do_store_64(DisasContext *ctx, TCGv_i64 src, unsigned rb,
                        unsigned rx, int scale, target_long disp,
                        int modify, TCGMemOp mop)
{
    TCGv addr, base;

    /* Caller uses nullify_over/nullify_end.  */
    assert(ctx->null_cond.c == TCG_COND_NEVER);

    addr = tcg_temp_new();
    base = load_gpr(ctx, rb);

    /* Note that RX is mutually exclusive with DISP.  */
    if (rx) {
        tcg_gen_shli_tl(addr, cpu_gr[rx], scale);
        tcg_gen_add_tl(addr, addr, base);
    } else {
        tcg_gen_addi_tl(addr, base, disp);
    }

    tcg_gen_qemu_st_i64(src, (modify <= 0 ? addr : base), MMU_USER_IDX, mop);

    if (modify != 0) {
        save_gpr(ctx, rb, addr);
    }
    tcg_temp_free(addr);
}

#if TARGET_LONG_BITS == 64
#define do_load_tl  do_load_64
#define do_store_tl do_store_64
#else
#define do_load_tl  do_load_32
#define do_store_tl do_store_32
#endif

static ExitStatus do_load(DisasContext *ctx, unsigned rt, unsigned rb,
                          unsigned rx, int scale, target_long disp,
                          int modify, TCGMemOp mop)
{
    TCGv dest;

    nullify_over(ctx);

    if (modify == 0) {
        /* No base register update.  */
        dest = dest_gpr(ctx, rt);
    } else {
        /* Make sure if RT == RB, we see the result of the load.  */
        dest = get_temp(ctx);
    }
    do_load_tl(ctx, dest, rb, rx, scale, disp, modify, mop);
    save_gpr(ctx, rt, dest);

    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus do_floadw(DisasContext *ctx, unsigned rt, unsigned rb,
                            unsigned rx, int scale, target_long disp,
                            int modify)
{
    TCGv_i32 tmp;

    nullify_over(ctx);

    tmp = tcg_temp_new_i32();
    do_load_32(ctx, tmp, rb, rx, scale, disp, modify, MO_TEUL);
    save_frw_i32(rt, tmp);
    tcg_temp_free_i32(tmp);

    if (rt == 0) {
        gen_helper_loaded_fr0(cpu_env);
    }

    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus do_floadd(DisasContext *ctx, unsigned rt, unsigned rb,
                            unsigned rx, int scale, target_long disp,
                            int modify)
{
    TCGv_i64 tmp;

    nullify_over(ctx);

    tmp = tcg_temp_new_i64();
    do_load_64(ctx, tmp, rb, rx, scale, disp, modify, MO_TEQ);
    save_frd(rt, tmp);
    tcg_temp_free_i64(tmp);

    if (rt == 0) {
        gen_helper_loaded_fr0(cpu_env);
    }

    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus do_store(DisasContext *ctx, unsigned rt, unsigned rb,
                           target_long disp, int modify, TCGMemOp mop)
{
    nullify_over(ctx);
    do_store_tl(ctx, load_gpr(ctx, rt), rb, 0, 0, disp, modify, mop);
    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus do_fstorew(DisasContext *ctx, unsigned rt, unsigned rb,
                             unsigned rx, int scale, target_long disp,
                             int modify)
{
    TCGv_i32 tmp;

    nullify_over(ctx);

    tmp = load_frw_i32(rt);
    do_store_32(ctx, tmp, rb, rx, scale, disp, modify, MO_TEUL);
    tcg_temp_free_i32(tmp);

    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus do_fstored(DisasContext *ctx, unsigned rt, unsigned rb,
                             unsigned rx, int scale, target_long disp,
                             int modify)
{
    TCGv_i64 tmp;

    nullify_over(ctx);

    tmp = load_frd(rt);
    do_store_64(ctx, tmp, rb, rx, scale, disp, modify, MO_TEQ);
    tcg_temp_free_i64(tmp);

    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus do_fop_wew(DisasContext *ctx, unsigned rt, unsigned ra,
                             void (*func)(TCGv_i32, TCGv_env, TCGv_i32))
{
    TCGv_i32 tmp;

    nullify_over(ctx);
    tmp = load_frw0_i32(ra);

    func(tmp, cpu_env, tmp);

    save_frw_i32(rt, tmp);
    tcg_temp_free_i32(tmp);
    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus do_fop_wed(DisasContext *ctx, unsigned rt, unsigned ra,
                             void (*func)(TCGv_i32, TCGv_env, TCGv_i64))
{
    TCGv_i32 dst;
    TCGv_i64 src;

    nullify_over(ctx);
    src = load_frd(ra);
    dst = tcg_temp_new_i32();

    func(dst, cpu_env, src);

    tcg_temp_free_i64(src);
    save_frw_i32(rt, dst);
    tcg_temp_free_i32(dst);
    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus do_fop_ded(DisasContext *ctx, unsigned rt, unsigned ra,
                             void (*func)(TCGv_i64, TCGv_env, TCGv_i64))
{
    TCGv_i64 tmp;

    nullify_over(ctx);
    tmp = load_frd0(ra);

    func(tmp, cpu_env, tmp);

    save_frd(rt, tmp);
    tcg_temp_free_i64(tmp);
    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus do_fop_dew(DisasContext *ctx, unsigned rt, unsigned ra,
                             void (*func)(TCGv_i64, TCGv_env, TCGv_i32))
{
    TCGv_i32 src;
    TCGv_i64 dst;

    nullify_over(ctx);
    src = load_frw0_i32(ra);
    dst = tcg_temp_new_i64();

    func(dst, cpu_env, src);

    tcg_temp_free_i32(src);
    save_frd(rt, dst);
    tcg_temp_free_i64(dst);
    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus do_fop_weww(DisasContext *ctx, unsigned rt,
                              unsigned ra, unsigned rb,
                              void (*func)(TCGv_i32, TCGv_env,
                                           TCGv_i32, TCGv_i32))
{
    TCGv_i32 a, b;

    nullify_over(ctx);
    a = load_frw0_i32(ra);
    b = load_frw0_i32(rb);

    func(a, cpu_env, a, b);

    tcg_temp_free_i32(b);
    save_frw_i32(rt, a);
    tcg_temp_free_i32(a);
    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus do_fop_dedd(DisasContext *ctx, unsigned rt,
                              unsigned ra, unsigned rb,
                              void (*func)(TCGv_i64, TCGv_env,
                                           TCGv_i64, TCGv_i64))
{
    TCGv_i64 a, b;

    nullify_over(ctx);
    a = load_frd0(ra);
    b = load_frd0(rb);

    func(a, cpu_env, a, b);

    tcg_temp_free_i64(b);
    save_frd(rt, a);
    tcg_temp_free_i64(a);
    return nullify_end(ctx, NO_EXIT);
}

/* Emit an unconditional branch to a direct target, which may or may not
   have already had nullification handled.  */
static ExitStatus do_dbranch(DisasContext *ctx, target_ulong dest,
                             unsigned link, bool is_n)
{
    if (ctx->null_cond.c == TCG_COND_NEVER && ctx->null_lab == NULL) {
        if (link != 0) {
            copy_iaoq_entry(cpu_gr[link], ctx->iaoq_n, ctx->iaoq_n_var);
        }
        ctx->iaoq_n = dest;
        if (is_n) {
            ctx->null_cond.c = TCG_COND_ALWAYS;
        }
        return NO_EXIT;
    } else {
        nullify_over(ctx);

        if (link != 0) {
            copy_iaoq_entry(cpu_gr[link], ctx->iaoq_n, ctx->iaoq_n_var);
        }

        if (is_n && use_nullify_skip(ctx)) {
            nullify_set(ctx, 0);
            gen_goto_tb(ctx, 0, dest, dest + 4);
        } else {
            nullify_set(ctx, is_n);
            gen_goto_tb(ctx, 0, ctx->iaoq_b, dest);
        }

        nullify_end(ctx, NO_EXIT);

        nullify_set(ctx, 0);
        gen_goto_tb(ctx, 1, ctx->iaoq_b, ctx->iaoq_n);
        return EXIT_GOTO_TB;
    }
}

/* Emit a conditional branch to a direct target.  If the branch itself
   is nullified, we should have already used nullify_over.  */
static ExitStatus do_cbranch(DisasContext *ctx, target_long disp, bool is_n,
                             DisasCond *cond)
{
    target_ulong dest = iaoq_dest(ctx, disp);
    TCGLabel *taken = NULL;
    TCGCond c = cond->c;
    bool n;

    assert(ctx->null_cond.c == TCG_COND_NEVER);

    /* Handle TRUE and NEVER as direct branches.  */
    if (c == TCG_COND_ALWAYS) {
        return do_dbranch(ctx, dest, 0, is_n && disp >= 0);
    }
    if (c == TCG_COND_NEVER) {
        return do_dbranch(ctx, ctx->iaoq_n, 0, is_n && disp < 0);
    }

    taken = gen_new_label();
    cond_prep(cond);
    tcg_gen_brcond_tl(c, cond->a0, cond->a1, taken);
    cond_free(cond);

    /* Not taken: Condition not satisfied; nullify on backward branches. */
    n = is_n && disp < 0;
    if (n && use_nullify_skip(ctx)) {
        nullify_set(ctx, 0);
        gen_goto_tb(ctx, 0, ctx->iaoq_n, ctx->iaoq_n + 4);
    } else {
        if (!n && ctx->null_lab) {
            gen_set_label(ctx->null_lab);
            ctx->null_lab = NULL;
        }
        nullify_set(ctx, n);
        gen_goto_tb(ctx, 0, ctx->iaoq_b, ctx->iaoq_n);
    }

    gen_set_label(taken);

    /* Taken: Condition satisfied; nullify on forward branches.  */
    n = is_n && disp >= 0;
    if (n && use_nullify_skip(ctx)) {
        nullify_set(ctx, 0);
        gen_goto_tb(ctx, 1, dest, dest + 4);
    } else {
        nullify_set(ctx, n);
        gen_goto_tb(ctx, 1, ctx->iaoq_b, dest);
    }

    /* Not taken: the branch itself was nullified.  */
    if (ctx->null_lab) {
        gen_set_label(ctx->null_lab);
        ctx->null_lab = NULL;
        return EXIT_IAQ_N_STALE;
    } else {
        return EXIT_GOTO_TB;
    }
}

/* Emit an unconditional branch to an indirect target.  This handles
   nullification of the branch itself.  */
static ExitStatus do_ibranch(DisasContext *ctx, TCGv dest,
                             unsigned link, bool is_n)
{
    TCGv a0, a1, next, tmp;
    TCGCond c;

    assert(ctx->null_lab == NULL);

    if (ctx->null_cond.c == TCG_COND_NEVER) {
        if (link != 0) {
            copy_iaoq_entry(cpu_gr[link], ctx->iaoq_n, ctx->iaoq_n_var);
        }
        next = get_temp(ctx);
        tcg_gen_mov_tl(next, dest);
        ctx->iaoq_n = -1;
        ctx->iaoq_n_var = next;
        if (is_n) {
            ctx->null_cond.c = TCG_COND_ALWAYS;
        }
    } else if (is_n && use_nullify_skip(ctx)) {
        /* The (conditional) branch, B, nullifies the next insn, N,
           and we're allowed to skip execution N (no single-step or
           tracepoint in effect).  Since the exit_tb that we must use
           for the indirect branch consumes no special resources, we
           can (conditionally) skip B and continue execution.  */
        /* The use_nullify_skip test implies we have a known control path.  */
        tcg_debug_assert(ctx->iaoq_b != -1);
        tcg_debug_assert(ctx->iaoq_n != -1);

        /* We do have to handle the non-local temporary, DEST, before
           branching.  Since IOAQ_F is not really live at this point, we
           can simply store DEST optimistically.  Similarly with IAOQ_B.  */
        tcg_gen_mov_tl(cpu_iaoq_f, dest);
        tcg_gen_addi_tl(cpu_iaoq_b, dest, 4);

        nullify_over(ctx);
        if (link != 0) {
            tcg_gen_movi_tl(cpu_gr[link], ctx->iaoq_n);
        }
        tcg_gen_exit_tb(0);
        return nullify_end(ctx, NO_EXIT);
    } else {
        cond_prep(&ctx->null_cond);
        c = ctx->null_cond.c;
        a0 = ctx->null_cond.a0;
        a1 = ctx->null_cond.a1;

        tmp = tcg_temp_new();
        next = get_temp(ctx);

        copy_iaoq_entry(tmp, ctx->iaoq_n, ctx->iaoq_n_var);
        tcg_gen_movcond_tl(c, next, a0, a1, tmp, dest);
        ctx->iaoq_n = -1;
        ctx->iaoq_n_var = next;

        if (link != 0) {
            tcg_gen_movcond_tl(c, cpu_gr[link], a0, a1, cpu_gr[link], tmp);
        }

        if (is_n) {
            /* The branch nullifies the next insn, which means the state of N
               after the branch is the inverse of the state of N that applied
               to the branch.  */
            tcg_gen_setcond_tl(tcg_invert_cond(c), cpu_psw_n, a0, a1);
            cond_free(&ctx->null_cond);
            ctx->null_cond = cond_make_n();
            ctx->psw_n_nonzero = true;
        } else {
            cond_free(&ctx->null_cond);
        }
    }

    return NO_EXIT;
}

/* On Linux, page zero is normally marked execute only + gateway.
   Therefore normal read or write is supposed to fail, but specific
   offsets have kernel code mapped to raise permissions to implement
   system calls.  Handling this via an explicit check here, rather
   in than the "be disp(sr2,r0)" instruction that probably sent us
   here, is the easiest way to handle the branch delay slot on the
   aforementioned BE.  */
static ExitStatus do_page_zero(DisasContext *ctx)
{
    /* If by some means we get here with PSW[N]=1, that implies that
       the B,GATE instruction would be skipped, and we'd fault on the
       next insn within the privilaged page.  */
    switch (ctx->null_cond.c) {
    case TCG_COND_NEVER:
        break;
    case TCG_COND_ALWAYS:
        tcg_gen_movi_tl(cpu_psw_n, 0);
        goto do_sigill;
    default:
        /* Since this is always the first (and only) insn within the
           TB, we should know the state of PSW[N] from TB->FLAGS.  */
        g_assert_not_reached();
    }

    /* Check that we didn't arrive here via some means that allowed
       non-sequential instruction execution.  Normally the PSW[B] bit
       detects this by disallowing the B,GATE instruction to execute
       under such conditions.  */
    if (ctx->iaoq_b != ctx->iaoq_f + 4) {
        goto do_sigill;
    }

    switch (ctx->iaoq_f) {
    case 0x00: /* Null pointer call */
        gen_excp_1(EXCP_SIGSEGV);
        return EXIT_NORETURN;

    case 0xb0: /* LWS */
        gen_excp_1(EXCP_SYSCALL_LWS);
        return EXIT_NORETURN;

    case 0xe0: /* SET_THREAD_POINTER */
        tcg_gen_mov_tl(cpu_cr27, cpu_gr[26]);
        tcg_gen_mov_tl(cpu_iaoq_f, cpu_gr[31]);
        tcg_gen_addi_tl(cpu_iaoq_b, cpu_iaoq_f, 4);
        return EXIT_IAQ_N_UPDATED;

    case 0x100: /* SYSCALL */
        gen_excp_1(EXCP_SYSCALL);
        return EXIT_NORETURN;

    default:
    do_sigill:
        gen_excp_1(EXCP_SIGILL);
        return EXIT_NORETURN;
    }
}

static ExitStatus trans_nop(DisasContext *ctx, uint32_t insn,
                            const DisasInsn *di)
{
    cond_free(&ctx->null_cond);
    return NO_EXIT;
}

static ExitStatus trans_break(DisasContext *ctx, uint32_t insn,
                              const DisasInsn *di)
{
    nullify_over(ctx);
    return nullify_end(ctx, gen_excp(ctx, EXCP_DEBUG));
}

static ExitStatus trans_sync(DisasContext *ctx, uint32_t insn,
                             const DisasInsn *di)
{
    /* No point in nullifying the memory barrier.  */
    tcg_gen_mb(TCG_BAR_SC | TCG_MO_ALL);

    cond_free(&ctx->null_cond);
    return NO_EXIT;
}

static ExitStatus trans_mfia(DisasContext *ctx, uint32_t insn,
                             const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    TCGv tmp = dest_gpr(ctx, rt);
    tcg_gen_movi_tl(tmp, ctx->iaoq_f);
    save_gpr(ctx, rt, tmp);

    cond_free(&ctx->null_cond);
    return NO_EXIT;
}

static ExitStatus trans_mfsp(DisasContext *ctx, uint32_t insn,
                             const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    TCGv tmp = dest_gpr(ctx, rt);

    /* ??? We don't implement space registers.  */
    tcg_gen_movi_tl(tmp, 0);
    save_gpr(ctx, rt, tmp);

    cond_free(&ctx->null_cond);
    return NO_EXIT;
}

static ExitStatus trans_mfctl(DisasContext *ctx, uint32_t insn,
                              const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned ctl = extract32(insn, 21, 5);
    TCGv tmp;

    switch (ctl) {
    case 11: /* SAR */
#ifdef TARGET_HPPA64
        if (extract32(insn, 14, 1) == 0) {
            /* MFSAR without ,W masks low 5 bits.  */
            tmp = dest_gpr(ctx, rt);
            tcg_gen_andi_tl(tmp, cpu_sar, 31);
            save_gpr(ctx, rt, tmp);
            break;
        }
#endif
        save_gpr(ctx, rt, cpu_sar);
        break;
    case 16: /* Interval Timer */
        tmp = dest_gpr(ctx, rt);
        tcg_gen_movi_tl(tmp, 0); /* FIXME */
        save_gpr(ctx, rt, tmp);
        break;
    case 26:
        save_gpr(ctx, rt, cpu_cr26);
        break;
    case 27:
        save_gpr(ctx, rt, cpu_cr27);
        break;
    default:
        /* All other control registers are privileged.  */
        return gen_illegal(ctx);
    }

    cond_free(&ctx->null_cond);
    return NO_EXIT;
}

static ExitStatus trans_mtctl(DisasContext *ctx, uint32_t insn,
                              const DisasInsn *di)
{
    unsigned rin = extract32(insn, 16, 5);
    unsigned ctl = extract32(insn, 21, 5);
    TCGv tmp;

    if (ctl == 11) { /* SAR */
        tmp = tcg_temp_new();
        tcg_gen_andi_tl(tmp, load_gpr(ctx, rin), TARGET_LONG_BITS - 1);
        save_or_nullify(ctx, cpu_sar, tmp);
        tcg_temp_free(tmp);
    } else {
        /* All other control registers are privileged or read-only.  */
        return gen_illegal(ctx);
    }

    cond_free(&ctx->null_cond);
    return NO_EXIT;
}

static ExitStatus trans_mtsarcm(DisasContext *ctx, uint32_t insn,
                                const DisasInsn *di)
{
    unsigned rin = extract32(insn, 16, 5);
    TCGv tmp = tcg_temp_new();

    tcg_gen_not_tl(tmp, load_gpr(ctx, rin));
    tcg_gen_andi_tl(tmp, tmp, TARGET_LONG_BITS - 1);
    save_or_nullify(ctx, cpu_sar, tmp);
    tcg_temp_free(tmp);

    cond_free(&ctx->null_cond);
    return NO_EXIT;
}

static ExitStatus trans_ldsid(DisasContext *ctx, uint32_t insn,
                              const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    TCGv dest = dest_gpr(ctx, rt);

    /* Since we don't implement space registers, this returns zero.  */
    tcg_gen_movi_tl(dest, 0);
    save_gpr(ctx, rt, dest);

    cond_free(&ctx->null_cond);
    return NO_EXIT;
}

static const DisasInsn table_system[] = {
    { 0x00000000u, 0xfc001fe0u, trans_break },
    /* We don't implement space register, so MTSP is a nop.  */
    { 0x00001820u, 0xffe01fffu, trans_nop },
    { 0x00001840u, 0xfc00ffffu, trans_mtctl },
    { 0x016018c0u, 0xffe0ffffu, trans_mtsarcm },
    { 0x000014a0u, 0xffffffe0u, trans_mfia },
    { 0x000004a0u, 0xffff1fe0u, trans_mfsp },
    { 0x000008a0u, 0xfc1fffe0u, trans_mfctl },
    { 0x00000400u, 0xffffffffu, trans_sync },
    { 0x000010a0u, 0xfc1f3fe0u, trans_ldsid },
};

static ExitStatus trans_base_idx_mod(DisasContext *ctx, uint32_t insn,
                                     const DisasInsn *di)
{
    unsigned rb = extract32(insn, 21, 5);
    unsigned rx = extract32(insn, 16, 5);
    TCGv dest = dest_gpr(ctx, rb);
    TCGv src1 = load_gpr(ctx, rb);
    TCGv src2 = load_gpr(ctx, rx);

    /* The only thing we need to do is the base register modification.  */
    tcg_gen_add_tl(dest, src1, src2);
    save_gpr(ctx, rb, dest);

    cond_free(&ctx->null_cond);
    return NO_EXIT;
}

static ExitStatus trans_probe(DisasContext *ctx, uint32_t insn,
                              const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned rb = extract32(insn, 21, 5);
    unsigned is_write = extract32(insn, 6, 1);
    TCGv dest;

    nullify_over(ctx);

    /* ??? Do something with priv level operand.  */
    dest = dest_gpr(ctx, rt);
    if (is_write) {
        gen_helper_probe_w(dest, load_gpr(ctx, rb));
    } else {
        gen_helper_probe_r(dest, load_gpr(ctx, rb));
    }
    save_gpr(ctx, rt, dest);
    return nullify_end(ctx, NO_EXIT);
}

static const DisasInsn table_mem_mgmt[] = {
    { 0x04003280u, 0xfc003fffu, trans_nop },          /* fdc, disp */
    { 0x04001280u, 0xfc003fffu, trans_nop },          /* fdc, index */
    { 0x040012a0u, 0xfc003fffu, trans_base_idx_mod }, /* fdc, index, base mod */
    { 0x040012c0u, 0xfc003fffu, trans_nop },          /* fdce */
    { 0x040012e0u, 0xfc003fffu, trans_base_idx_mod }, /* fdce, base mod */
    { 0x04000280u, 0xfc001fffu, trans_nop },          /* fic 0a */
    { 0x040002a0u, 0xfc001fffu, trans_base_idx_mod }, /* fic 0a, base mod */
    { 0x040013c0u, 0xfc003fffu, trans_nop },          /* fic 4f */
    { 0x040013e0u, 0xfc003fffu, trans_base_idx_mod }, /* fic 4f, base mod */
    { 0x040002c0u, 0xfc001fffu, trans_nop },          /* fice */
    { 0x040002e0u, 0xfc001fffu, trans_base_idx_mod }, /* fice, base mod */
    { 0x04002700u, 0xfc003fffu, trans_nop },          /* pdc */
    { 0x04002720u, 0xfc003fffu, trans_base_idx_mod }, /* pdc, base mod */
    { 0x04001180u, 0xfc003fa0u, trans_probe },        /* probe */
    { 0x04003180u, 0xfc003fa0u, trans_probe },        /* probei */
};

static ExitStatus trans_add(DisasContext *ctx, uint32_t insn,
                            const DisasInsn *di)
{
    unsigned r2 = extract32(insn, 21, 5);
    unsigned r1 = extract32(insn, 16, 5);
    unsigned cf = extract32(insn, 12, 4);
    unsigned ext = extract32(insn, 8, 4);
    unsigned shift = extract32(insn, 6, 2);
    unsigned rt = extract32(insn,  0, 5);
    TCGv tcg_r1, tcg_r2;
    bool is_c = false;
    bool is_l = false;
    bool is_tc = false;
    bool is_tsv = false;
    ExitStatus ret;

    switch (ext) {
    case 0x6: /* ADD, SHLADD */
        break;
    case 0xa: /* ADD,L, SHLADD,L */
        is_l = true;
        break;
    case 0xe: /* ADD,TSV, SHLADD,TSV (1) */
        is_tsv = true;
        break;
    case 0x7: /* ADD,C */
        is_c = true;
        break;
    case 0xf: /* ADD,C,TSV */
        is_c = is_tsv = true;
        break;
    default:
        return gen_illegal(ctx);
    }

    if (cf) {
        nullify_over(ctx);
    }
    tcg_r1 = load_gpr(ctx, r1);
    tcg_r2 = load_gpr(ctx, r2);
    ret = do_add(ctx, rt, tcg_r1, tcg_r2, shift, is_l, is_tsv, is_tc, is_c, cf);
    return nullify_end(ctx, ret);
}

static ExitStatus trans_sub(DisasContext *ctx, uint32_t insn,
                            const DisasInsn *di)
{
    unsigned r2 = extract32(insn, 21, 5);
    unsigned r1 = extract32(insn, 16, 5);
    unsigned cf = extract32(insn, 12, 4);
    unsigned ext = extract32(insn, 6, 6);
    unsigned rt = extract32(insn,  0, 5);
    TCGv tcg_r1, tcg_r2;
    bool is_b = false;
    bool is_tc = false;
    bool is_tsv = false;
    ExitStatus ret;

    switch (ext) {
    case 0x10: /* SUB */
        break;
    case 0x30: /* SUB,TSV */
        is_tsv = true;
        break;
    case 0x14: /* SUB,B */
        is_b = true;
        break;
    case 0x34: /* SUB,B,TSV */
        is_b = is_tsv = true;
        break;
    case 0x13: /* SUB,TC */
        is_tc = true;
        break;
    case 0x33: /* SUB,TSV,TC */
        is_tc = is_tsv = true;
        break;
    default:
        return gen_illegal(ctx);
    }

    if (cf) {
        nullify_over(ctx);
    }
    tcg_r1 = load_gpr(ctx, r1);
    tcg_r2 = load_gpr(ctx, r2);
    ret = do_sub(ctx, rt, tcg_r1, tcg_r2, is_tsv, is_b, is_tc, cf);
    return nullify_end(ctx, ret);
}

static ExitStatus trans_log(DisasContext *ctx, uint32_t insn,
                            const DisasInsn *di)
{
    unsigned r2 = extract32(insn, 21, 5);
    unsigned r1 = extract32(insn, 16, 5);
    unsigned cf = extract32(insn, 12, 4);
    unsigned rt = extract32(insn,  0, 5);
    TCGv tcg_r1, tcg_r2;
    ExitStatus ret;

    if (cf) {
        nullify_over(ctx);
    }
    tcg_r1 = load_gpr(ctx, r1);
    tcg_r2 = load_gpr(ctx, r2);
    ret = do_log(ctx, rt, tcg_r1, tcg_r2, cf, di->f_ttt);
    return nullify_end(ctx, ret);
}

/* OR r,0,t -> COPY (according to gas) */
static ExitStatus trans_copy(DisasContext *ctx, uint32_t insn,
                             const DisasInsn *di)
{
    unsigned r1 = extract32(insn, 16, 5);
    unsigned rt = extract32(insn,  0, 5);

    if (r1 == 0) {
        TCGv dest = dest_gpr(ctx, rt);
        tcg_gen_movi_tl(dest, 0);
        save_gpr(ctx, rt, dest);
    } else {
        save_gpr(ctx, rt, cpu_gr[r1]);
    }
    cond_free(&ctx->null_cond);
    return NO_EXIT;
}

static ExitStatus trans_cmpclr(DisasContext *ctx, uint32_t insn,
                               const DisasInsn *di)
{
    unsigned r2 = extract32(insn, 21, 5);
    unsigned r1 = extract32(insn, 16, 5);
    unsigned cf = extract32(insn, 12, 4);
    unsigned rt = extract32(insn,  0, 5);
    TCGv tcg_r1, tcg_r2;
    ExitStatus ret;

    if (cf) {
        nullify_over(ctx);
    }
    tcg_r1 = load_gpr(ctx, r1);
    tcg_r2 = load_gpr(ctx, r2);
    ret = do_cmpclr(ctx, rt, tcg_r1, tcg_r2, cf);
    return nullify_end(ctx, ret);
}

static ExitStatus trans_uxor(DisasContext *ctx, uint32_t insn,
                             const DisasInsn *di)
{
    unsigned r2 = extract32(insn, 21, 5);
    unsigned r1 = extract32(insn, 16, 5);
    unsigned cf = extract32(insn, 12, 4);
    unsigned rt = extract32(insn,  0, 5);
    TCGv tcg_r1, tcg_r2;
    ExitStatus ret;

    if (cf) {
        nullify_over(ctx);
    }
    tcg_r1 = load_gpr(ctx, r1);
    tcg_r2 = load_gpr(ctx, r2);
    ret = do_unit(ctx, rt, tcg_r1, tcg_r2, cf, false, tcg_gen_xor_tl);
    return nullify_end(ctx, ret);
}

static ExitStatus trans_uaddcm(DisasContext *ctx, uint32_t insn,
                               const DisasInsn *di)
{
    unsigned r2 = extract32(insn, 21, 5);
    unsigned r1 = extract32(insn, 16, 5);
    unsigned cf = extract32(insn, 12, 4);
    unsigned is_tc = extract32(insn, 6, 1);
    unsigned rt = extract32(insn,  0, 5);
    TCGv tcg_r1, tcg_r2, tmp;
    ExitStatus ret;

    if (cf) {
        nullify_over(ctx);
    }
    tcg_r1 = load_gpr(ctx, r1);
    tcg_r2 = load_gpr(ctx, r2);
    tmp = get_temp(ctx);
    tcg_gen_not_tl(tmp, tcg_r2);
    ret = do_unit(ctx, rt, tcg_r1, tmp, cf, is_tc, tcg_gen_add_tl);
    return nullify_end(ctx, ret);
}

static ExitStatus trans_dcor(DisasContext *ctx, uint32_t insn,
                             const DisasInsn *di)
{
    unsigned r2 = extract32(insn, 21, 5);
    unsigned cf = extract32(insn, 12, 4);
    unsigned is_i = extract32(insn, 6, 1);
    unsigned rt = extract32(insn,  0, 5);
    TCGv tmp;
    ExitStatus ret;

    nullify_over(ctx);

    tmp = get_temp(ctx);
    tcg_gen_shri_tl(tmp, cpu_psw_cb, 3);
    if (!is_i) {
        tcg_gen_not_tl(tmp, tmp);
    }
    tcg_gen_andi_tl(tmp, tmp, 0x11111111);
    tcg_gen_muli_tl(tmp, tmp, 6);
    ret = do_unit(ctx, rt, tmp, load_gpr(ctx, r2), cf, false,
                  is_i ? tcg_gen_add_tl : tcg_gen_sub_tl);

    return nullify_end(ctx, ret);
}

static ExitStatus trans_ds(DisasContext *ctx, uint32_t insn,
                           const DisasInsn *di)
{
    unsigned r2 = extract32(insn, 21, 5);
    unsigned r1 = extract32(insn, 16, 5);
    unsigned cf = extract32(insn, 12, 4);
    unsigned rt = extract32(insn,  0, 5);
    TCGv dest, add1, add2, addc, zero, in1, in2;

    nullify_over(ctx);

    in1 = load_gpr(ctx, r1);
    in2 = load_gpr(ctx, r2);

    add1 = tcg_temp_new();
    add2 = tcg_temp_new();
    addc = tcg_temp_new();
    dest = tcg_temp_new();
    zero = tcg_const_tl(0);

    /* Form R1 << 1 | PSW[CB]{8}.  */
    tcg_gen_add_tl(add1, in1, in1);
    tcg_gen_add_tl(add1, add1, cpu_psw_cb_msb);

    /* Add or subtract R2, depending on PSW[V].  Proper computation of
       carry{8} requires that we subtract via + ~R2 + 1, as described in
       the manual.  By extracting and masking V, we can produce the
       proper inputs to the addition without movcond.  */
    tcg_gen_sari_tl(addc, cpu_psw_v, TARGET_LONG_BITS - 1);
    tcg_gen_xor_tl(add2, in2, addc);
    tcg_gen_andi_tl(addc, addc, 1);
    /* ??? This is only correct for 32-bit.  */
    tcg_gen_add2_i32(dest, cpu_psw_cb_msb, add1, zero, add2, zero);
    tcg_gen_add2_i32(dest, cpu_psw_cb_msb, dest, cpu_psw_cb_msb, addc, zero);

    tcg_temp_free(addc);
    tcg_temp_free(zero);

    /* Write back the result register.  */
    save_gpr(ctx, rt, dest);

    /* Write back PSW[CB].  */
    tcg_gen_xor_tl(cpu_psw_cb, add1, add2);
    tcg_gen_xor_tl(cpu_psw_cb, cpu_psw_cb, dest);

    /* Write back PSW[V] for the division step.  */
    tcg_gen_neg_tl(cpu_psw_v, cpu_psw_cb_msb);
    tcg_gen_xor_tl(cpu_psw_v, cpu_psw_v, in2);

    /* Install the new nullification.  */
    if (cf) {
        TCGv sv;
        TCGV_UNUSED(sv);
        if (cf >> 1 == 6) {
            /* ??? The lshift is supposed to contribute to overflow.  */
            sv = do_add_sv(ctx, dest, add1, add2);
        }
        ctx->null_cond = do_cond(cf, dest, cpu_psw_cb_msb, sv);
    }

    tcg_temp_free(add1);
    tcg_temp_free(add2);
    tcg_temp_free(dest);

    return nullify_end(ctx, NO_EXIT);
}

static const DisasInsn table_arith_log[] = {
    { 0x08000240u, 0xfc00ffffu, trans_nop },  /* or x,y,0 */
    { 0x08000240u, 0xffe0ffe0u, trans_copy }, /* or x,0,t */
    { 0x08000000u, 0xfc000fe0u, trans_log, .f_ttt = tcg_gen_andc_tl },
    { 0x08000200u, 0xfc000fe0u, trans_log, .f_ttt = tcg_gen_and_tl },
    { 0x08000240u, 0xfc000fe0u, trans_log, .f_ttt = tcg_gen_or_tl },
    { 0x08000280u, 0xfc000fe0u, trans_log, .f_ttt = tcg_gen_xor_tl },
    { 0x08000880u, 0xfc000fe0u, trans_cmpclr },
    { 0x08000380u, 0xfc000fe0u, trans_uxor },
    { 0x08000980u, 0xfc000fa0u, trans_uaddcm },
    { 0x08000b80u, 0xfc1f0fa0u, trans_dcor },
    { 0x08000440u, 0xfc000fe0u, trans_ds },
    { 0x08000700u, 0xfc0007e0u, trans_add }, /* add */
    { 0x08000400u, 0xfc0006e0u, trans_sub }, /* sub; sub,b; sub,tsv */
    { 0x080004c0u, 0xfc0007e0u, trans_sub }, /* sub,tc; sub,tsv,tc */
    { 0x08000200u, 0xfc000320u, trans_add }, /* shladd */
};

static ExitStatus trans_addi(DisasContext *ctx, uint32_t insn)
{
    target_long im = low_sextract(insn, 0, 11);
    unsigned e1 = extract32(insn, 11, 1);
    unsigned cf = extract32(insn, 12, 4);
    unsigned rt = extract32(insn, 16, 5);
    unsigned r2 = extract32(insn, 21, 5);
    unsigned o1 = extract32(insn, 26, 1);
    TCGv tcg_im, tcg_r2;
    ExitStatus ret;

    if (cf) {
        nullify_over(ctx);
    }

    tcg_im = load_const(ctx, im);
    tcg_r2 = load_gpr(ctx, r2);
    ret = do_add(ctx, rt, tcg_im, tcg_r2, 0, false, e1, !o1, false, cf);

    return nullify_end(ctx, ret);
}

static ExitStatus trans_subi(DisasContext *ctx, uint32_t insn)
{
    target_long im = low_sextract(insn, 0, 11);
    unsigned e1 = extract32(insn, 11, 1);
    unsigned cf = extract32(insn, 12, 4);
    unsigned rt = extract32(insn, 16, 5);
    unsigned r2 = extract32(insn, 21, 5);
    TCGv tcg_im, tcg_r2;
    ExitStatus ret;

    if (cf) {
        nullify_over(ctx);
    }

    tcg_im = load_const(ctx, im);
    tcg_r2 = load_gpr(ctx, r2);
    ret = do_sub(ctx, rt, tcg_im, tcg_r2, e1, false, false, cf);

    return nullify_end(ctx, ret);
}

static ExitStatus trans_cmpiclr(DisasContext *ctx, uint32_t insn)
{
    target_long im = low_sextract(insn, 0, 11);
    unsigned cf = extract32(insn, 12, 4);
    unsigned rt = extract32(insn, 16, 5);
    unsigned r2 = extract32(insn, 21, 5);
    TCGv tcg_im, tcg_r2;
    ExitStatus ret;

    if (cf) {
        nullify_over(ctx);
    }

    tcg_im = load_const(ctx, im);
    tcg_r2 = load_gpr(ctx, r2);
    ret = do_cmpclr(ctx, rt, tcg_im, tcg_r2, cf);

    return nullify_end(ctx, ret);
}

static ExitStatus trans_ld_idx_i(DisasContext *ctx, uint32_t insn,
                                 const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned m = extract32(insn, 5, 1);
    unsigned sz = extract32(insn, 6, 2);
    unsigned a = extract32(insn, 13, 1);
    int disp = low_sextract(insn, 16, 5);
    unsigned rb = extract32(insn, 21, 5);
    int modify = (m ? (a ? -1 : 1) : 0);
    TCGMemOp mop = MO_TE | sz;

    return do_load(ctx, rt, rb, 0, 0, disp, modify, mop);
}

static ExitStatus trans_ld_idx_x(DisasContext *ctx, uint32_t insn,
                                 const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned m = extract32(insn, 5, 1);
    unsigned sz = extract32(insn, 6, 2);
    unsigned u = extract32(insn, 13, 1);
    unsigned rx = extract32(insn, 16, 5);
    unsigned rb = extract32(insn, 21, 5);
    TCGMemOp mop = MO_TE | sz;

    return do_load(ctx, rt, rb, rx, u ? sz : 0, 0, m, mop);
}

static ExitStatus trans_st_idx_i(DisasContext *ctx, uint32_t insn,
                                 const DisasInsn *di)
{
    int disp = low_sextract(insn, 0, 5);
    unsigned m = extract32(insn, 5, 1);
    unsigned sz = extract32(insn, 6, 2);
    unsigned a = extract32(insn, 13, 1);
    unsigned rr = extract32(insn, 16, 5);
    unsigned rb = extract32(insn, 21, 5);
    int modify = (m ? (a ? -1 : 1) : 0);
    TCGMemOp mop = MO_TE | sz;

    return do_store(ctx, rr, rb, disp, modify, mop);
}

static ExitStatus trans_ldcw(DisasContext *ctx, uint32_t insn,
                             const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned m = extract32(insn, 5, 1);
    unsigned i = extract32(insn, 12, 1);
    unsigned au = extract32(insn, 13, 1);
    unsigned rx = extract32(insn, 16, 5);
    unsigned rb = extract32(insn, 21, 5);
    TCGMemOp mop = MO_TEUL | MO_ALIGN_16;
    TCGv zero, addr, base, dest;
    int modify, disp = 0, scale = 0;

    nullify_over(ctx);

    /* ??? Share more code with do_load and do_load_{32,64}.  */

    if (i) {
        modify = (m ? (au ? -1 : 1) : 0);
        disp = low_sextract(rx, 0, 5);
        rx = 0;
    } else {
        modify = m;
        if (au) {
            scale = mop & MO_SIZE;
        }
    }
    if (modify) {
        /* Base register modification.  Make sure if RT == RB, we see
           the result of the load.  */
        dest = get_temp(ctx);
    } else {
        dest = dest_gpr(ctx, rt);
    }

    addr = tcg_temp_new();
    base = load_gpr(ctx, rb);
    if (rx) {
        tcg_gen_shli_tl(addr, cpu_gr[rx], scale);
        tcg_gen_add_tl(addr, addr, base);
    } else {
        tcg_gen_addi_tl(addr, base, disp);
    }

    zero = tcg_const_tl(0);
    tcg_gen_atomic_xchg_tl(dest, (modify <= 0 ? addr : base),
                           zero, MMU_USER_IDX, mop);
    if (modify) {
        save_gpr(ctx, rb, addr);
    }
    save_gpr(ctx, rt, dest);

    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus trans_stby(DisasContext *ctx, uint32_t insn,
                             const DisasInsn *di)
{
    target_long disp = low_sextract(insn, 0, 5);
    unsigned m = extract32(insn, 5, 1);
    unsigned a = extract32(insn, 13, 1);
    unsigned rt = extract32(insn, 16, 5);
    unsigned rb = extract32(insn, 21, 5);
    TCGv addr, val;

    nullify_over(ctx);

    addr = tcg_temp_new();
    if (m || disp == 0) {
        tcg_gen_mov_tl(addr, load_gpr(ctx, rb));
    } else {
        tcg_gen_addi_tl(addr, load_gpr(ctx, rb), disp);
    }
    val = load_gpr(ctx, rt);

    if (a) {
        gen_helper_stby_e(cpu_env, addr, val);
    } else {
        gen_helper_stby_b(cpu_env, addr, val);
    }

    if (m) {
        tcg_gen_addi_tl(addr, addr, disp);
        tcg_gen_andi_tl(addr, addr, ~3);
        save_gpr(ctx, rb, addr);
    }
    tcg_temp_free(addr);

    return nullify_end(ctx, NO_EXIT);
}

static const DisasInsn table_index_mem[] = {
    { 0x0c001000u, 0xfc001300, trans_ld_idx_i }, /* LD[BHWD], im */
    { 0x0c000000u, 0xfc001300, trans_ld_idx_x }, /* LD[BHWD], rx */
    { 0x0c001200u, 0xfc001300, trans_st_idx_i }, /* ST[BHWD] */
    { 0x0c0001c0u, 0xfc0003c0, trans_ldcw },
    { 0x0c001300u, 0xfc0013c0, trans_stby },
};

static ExitStatus trans_ldil(DisasContext *ctx, uint32_t insn)
{
    unsigned rt = extract32(insn, 21, 5);
    target_long i = assemble_21(insn);
    TCGv tcg_rt = dest_gpr(ctx, rt);

    tcg_gen_movi_tl(tcg_rt, i);
    save_gpr(ctx, rt, tcg_rt);
    cond_free(&ctx->null_cond);

    return NO_EXIT;
}

static ExitStatus trans_addil(DisasContext *ctx, uint32_t insn)
{
    unsigned rt = extract32(insn, 21, 5);
    target_long i = assemble_21(insn);
    TCGv tcg_rt = load_gpr(ctx, rt);
    TCGv tcg_r1 = dest_gpr(ctx, 1);

    tcg_gen_addi_tl(tcg_r1, tcg_rt, i);
    save_gpr(ctx, 1, tcg_r1);
    cond_free(&ctx->null_cond);

    return NO_EXIT;
}

static ExitStatus trans_ldo(DisasContext *ctx, uint32_t insn)
{
    unsigned rb = extract32(insn, 21, 5);
    unsigned rt = extract32(insn, 16, 5);
    target_long i = assemble_16(insn);
    TCGv tcg_rt = dest_gpr(ctx, rt);

    /* Special case rb == 0, for the LDI pseudo-op.
       The COPY pseudo-op is handled for free within tcg_gen_addi_tl.  */
    if (rb == 0) {
        tcg_gen_movi_tl(tcg_rt, i);
    } else {
        tcg_gen_addi_tl(tcg_rt, cpu_gr[rb], i);
    }
    save_gpr(ctx, rt, tcg_rt);
    cond_free(&ctx->null_cond);

    return NO_EXIT;
}

static ExitStatus trans_load(DisasContext *ctx, uint32_t insn,
                             bool is_mod, TCGMemOp mop)
{
    unsigned rb = extract32(insn, 21, 5);
    unsigned rt = extract32(insn, 16, 5);
    target_long i = assemble_16(insn);

    return do_load(ctx, rt, rb, 0, 0, i, is_mod ? (i < 0 ? -1 : 1) : 0, mop);
}

static ExitStatus trans_load_w(DisasContext *ctx, uint32_t insn)
{
    unsigned rb = extract32(insn, 21, 5);
    unsigned rt = extract32(insn, 16, 5);
    target_long i = assemble_16a(insn);
    unsigned ext2 = extract32(insn, 1, 2);

    switch (ext2) {
    case 0:
    case 1:
        /* FLDW without modification.  */
        return do_floadw(ctx, ext2 * 32 + rt, rb, 0, 0, i, 0);
    case 2:
        /* LDW with modification.  Note that the sign of I selects
           post-dec vs pre-inc.  */
        return do_load(ctx, rt, rb, 0, 0, i, (i < 0 ? 1 : -1), MO_TEUL);
    default:
        return gen_illegal(ctx);
    }
}

static ExitStatus trans_fload_mod(DisasContext *ctx, uint32_t insn)
{
    target_long i = assemble_16a(insn);
    unsigned t1 = extract32(insn, 1, 1);
    unsigned a = extract32(insn, 2, 1);
    unsigned t0 = extract32(insn, 16, 5);
    unsigned rb = extract32(insn, 21, 5);

    /* FLDW with modification.  */
    return do_floadw(ctx, t1 * 32 + t0, rb, 0, 0, i, (a ? -1 : 1));
}

static ExitStatus trans_store(DisasContext *ctx, uint32_t insn,
                              bool is_mod, TCGMemOp mop)
{
    unsigned rb = extract32(insn, 21, 5);
    unsigned rt = extract32(insn, 16, 5);
    target_long i = assemble_16(insn);

    return do_store(ctx, rt, rb, i, is_mod ? (i < 0 ? -1 : 1) : 0, mop);
}

static ExitStatus trans_store_w(DisasContext *ctx, uint32_t insn)
{
    unsigned rb = extract32(insn, 21, 5);
    unsigned rt = extract32(insn, 16, 5);
    target_long i = assemble_16a(insn);
    unsigned ext2 = extract32(insn, 1, 2);

    switch (ext2) {
    case 0:
    case 1:
        /* FSTW without modification.  */
        return do_fstorew(ctx, ext2 * 32 + rt, rb, 0, 0, i, 0);
    case 2:
        /* LDW with modification.  */
        return do_store(ctx, rt, rb, i, (i < 0 ? 1 : -1), MO_TEUL);
    default:
        return gen_illegal(ctx);
    }
}

static ExitStatus trans_fstore_mod(DisasContext *ctx, uint32_t insn)
{
    target_long i = assemble_16a(insn);
    unsigned t1 = extract32(insn, 1, 1);
    unsigned a = extract32(insn, 2, 1);
    unsigned t0 = extract32(insn, 16, 5);
    unsigned rb = extract32(insn, 21, 5);

    /* FSTW with modification.  */
    return do_fstorew(ctx, t1 * 32 + t0, rb, 0, 0, i, (a ? -1 : 1));
}

static ExitStatus trans_copr_w(DisasContext *ctx, uint32_t insn)
{
    unsigned t0 = extract32(insn, 0, 5);
    unsigned m = extract32(insn, 5, 1);
    unsigned t1 = extract32(insn, 6, 1);
    unsigned ext3 = extract32(insn, 7, 3);
    /* unsigned cc = extract32(insn, 10, 2); */
    unsigned i = extract32(insn, 12, 1);
    unsigned ua = extract32(insn, 13, 1);
    unsigned rx = extract32(insn, 16, 5);
    unsigned rb = extract32(insn, 21, 5);
    unsigned rt = t1 * 32 + t0;
    int modify = (m ? (ua ? -1 : 1) : 0);
    int disp, scale;

    if (i == 0) {
        scale = (ua ? 2 : 0);
        disp = 0;
        modify = m;
    } else {
        disp = low_sextract(rx, 0, 5);
        scale = 0;
        rx = 0;
        modify = (m ? (ua ? -1 : 1) : 0);
    }

    switch (ext3) {
    case 0: /* FLDW */
        return do_floadw(ctx, rt, rb, rx, scale, disp, modify);
    case 4: /* FSTW */
        return do_fstorew(ctx, rt, rb, rx, scale, disp, modify);
    }
    return gen_illegal(ctx);
}

static ExitStatus trans_copr_dw(DisasContext *ctx, uint32_t insn)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned m = extract32(insn, 5, 1);
    unsigned ext4 = extract32(insn, 6, 4);
    /* unsigned cc = extract32(insn, 10, 2); */
    unsigned i = extract32(insn, 12, 1);
    unsigned ua = extract32(insn, 13, 1);
    unsigned rx = extract32(insn, 16, 5);
    unsigned rb = extract32(insn, 21, 5);
    int modify = (m ? (ua ? -1 : 1) : 0);
    int disp, scale;

    if (i == 0) {
        scale = (ua ? 3 : 0);
        disp = 0;
        modify = m;
    } else {
        disp = low_sextract(rx, 0, 5);
        scale = 0;
        rx = 0;
        modify = (m ? (ua ? -1 : 1) : 0);
    }

    switch (ext4) {
    case 0: /* FLDD */
        return do_floadd(ctx, rt, rb, rx, scale, disp, modify);
    case 8: /* FSTD */
        return do_fstored(ctx, rt, rb, rx, scale, disp, modify);
    default:
        return gen_illegal(ctx);
    }
}

static ExitStatus trans_cmpb(DisasContext *ctx, uint32_t insn,
                             bool is_true, bool is_imm, bool is_dw)
{
    target_long disp = assemble_12(insn) * 4;
    unsigned n = extract32(insn, 1, 1);
    unsigned c = extract32(insn, 13, 3);
    unsigned r = extract32(insn, 21, 5);
    unsigned cf = c * 2 + !is_true;
    TCGv dest, in1, in2, sv;
    DisasCond cond;

    nullify_over(ctx);

    if (is_imm) {
        in1 = load_const(ctx, low_sextract(insn, 16, 5));
    } else {
        in1 = load_gpr(ctx, extract32(insn, 16, 5));
    }
    in2 = load_gpr(ctx, r);
    dest = get_temp(ctx);

    tcg_gen_sub_tl(dest, in1, in2);

    TCGV_UNUSED(sv);
    if (c == 6) {
        sv = do_sub_sv(ctx, dest, in1, in2);
    }

    cond = do_sub_cond(cf, dest, in1, in2, sv);
    return do_cbranch(ctx, disp, n, &cond);
}

static ExitStatus trans_addb(DisasContext *ctx, uint32_t insn,
                             bool is_true, bool is_imm)
{
    target_long disp = assemble_12(insn) * 4;
    unsigned n = extract32(insn, 1, 1);
    unsigned c = extract32(insn, 13, 3);
    unsigned r = extract32(insn, 21, 5);
    unsigned cf = c * 2 + !is_true;
    TCGv dest, in1, in2, sv, cb_msb;
    DisasCond cond;

    nullify_over(ctx);

    if (is_imm) {
        in1 = load_const(ctx, low_sextract(insn, 16, 5));
    } else {
        in1 = load_gpr(ctx, extract32(insn, 16, 5));
    }
    in2 = load_gpr(ctx, r);
    dest = dest_gpr(ctx, r);
    TCGV_UNUSED(sv);
    TCGV_UNUSED(cb_msb);

    switch (c) {
    default:
        tcg_gen_add_tl(dest, in1, in2);
        break;
    case 4: case 5:
        cb_msb = get_temp(ctx);
        tcg_gen_movi_tl(cb_msb, 0);
        tcg_gen_add2_tl(dest, cb_msb, in1, cb_msb, in2, cb_msb);
        break;
    case 6:
        tcg_gen_add_tl(dest, in1, in2);
        sv = do_add_sv(ctx, dest, in1, in2);
        break;
    }

    cond = do_cond(cf, dest, cb_msb, sv);
    return do_cbranch(ctx, disp, n, &cond);
}

static ExitStatus trans_bb(DisasContext *ctx, uint32_t insn)
{
    target_long disp = assemble_12(insn) * 4;
    unsigned n = extract32(insn, 1, 1);
    unsigned c = extract32(insn, 15, 1);
    unsigned r = extract32(insn, 16, 5);
    unsigned p = extract32(insn, 21, 5);
    unsigned i = extract32(insn, 26, 1);
    TCGv tmp, tcg_r;
    DisasCond cond;

    nullify_over(ctx);

    tmp = tcg_temp_new();
    tcg_r = load_gpr(ctx, r);
    if (i) {
        tcg_gen_shli_tl(tmp, tcg_r, p);
    } else {
        tcg_gen_shl_tl(tmp, tcg_r, cpu_sar);
    }

    cond = cond_make_0(c ? TCG_COND_GE : TCG_COND_LT, tmp);
    tcg_temp_free(tmp);
    return do_cbranch(ctx, disp, n, &cond);
}

static ExitStatus trans_movb(DisasContext *ctx, uint32_t insn, bool is_imm)
{
    target_long disp = assemble_12(insn) * 4;
    unsigned n = extract32(insn, 1, 1);
    unsigned c = extract32(insn, 13, 3);
    unsigned t = extract32(insn, 16, 5);
    unsigned r = extract32(insn, 21, 5);
    TCGv dest;
    DisasCond cond;

    nullify_over(ctx);

    dest = dest_gpr(ctx, r);
    if (is_imm) {
        tcg_gen_movi_tl(dest, low_sextract(t, 0, 5));
    } else if (t == 0) {
        tcg_gen_movi_tl(dest, 0);
    } else {
        tcg_gen_mov_tl(dest, cpu_gr[t]);
    }

    cond = do_sed_cond(c, dest);
    return do_cbranch(ctx, disp, n, &cond);
}

static ExitStatus trans_shrpw_sar(DisasContext *ctx, uint32_t insn,
                                 const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned c = extract32(insn, 13, 3);
    unsigned r1 = extract32(insn, 16, 5);
    unsigned r2 = extract32(insn, 21, 5);
    TCGv dest;

    if (c) {
        nullify_over(ctx);
    }

    dest = dest_gpr(ctx, rt);
    if (r1 == 0) {
        tcg_gen_ext32u_tl(dest, load_gpr(ctx, r2));
        tcg_gen_shr_tl(dest, dest, cpu_sar);
    } else if (r1 == r2) {
        TCGv_i32 t32 = tcg_temp_new_i32();
        tcg_gen_trunc_tl_i32(t32, load_gpr(ctx, r2));
        tcg_gen_rotr_i32(t32, t32, cpu_sar);
        tcg_gen_extu_i32_tl(dest, t32);
        tcg_temp_free_i32(t32);
    } else {
        TCGv_i64 t = tcg_temp_new_i64();
        TCGv_i64 s = tcg_temp_new_i64();

        tcg_gen_concat_tl_i64(t, load_gpr(ctx, r2), load_gpr(ctx, r1));
        tcg_gen_extu_tl_i64(s, cpu_sar);
        tcg_gen_shr_i64(t, t, s);
        tcg_gen_trunc_i64_tl(dest, t);

        tcg_temp_free_i64(t);
        tcg_temp_free_i64(s);
    }
    save_gpr(ctx, rt, dest);

    /* Install the new nullification.  */
    cond_free(&ctx->null_cond);
    if (c) {
        ctx->null_cond = do_sed_cond(c, dest);
    }
    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus trans_shrpw_imm(DisasContext *ctx, uint32_t insn,
                                  const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned cpos = extract32(insn, 5, 5);
    unsigned c = extract32(insn, 13, 3);
    unsigned r1 = extract32(insn, 16, 5);
    unsigned r2 = extract32(insn, 21, 5);
    unsigned sa = 31 - cpos;
    TCGv dest, t2;

    if (c) {
        nullify_over(ctx);
    }

    dest = dest_gpr(ctx, rt);
    t2 = load_gpr(ctx, r2);
    if (r1 == r2) {
        TCGv_i32 t32 = tcg_temp_new_i32();
        tcg_gen_trunc_tl_i32(t32, t2);
        tcg_gen_rotri_i32(t32, t32, sa);
        tcg_gen_extu_i32_tl(dest, t32);
        tcg_temp_free_i32(t32);
    } else if (r1 == 0) {
        tcg_gen_extract_tl(dest, t2, sa, 32 - sa);
    } else {
        TCGv t0 = tcg_temp_new();
        tcg_gen_extract_tl(t0, t2, sa, 32 - sa);
        tcg_gen_deposit_tl(dest, t0, cpu_gr[r1], 32 - sa, sa);
        tcg_temp_free(t0);
    }
    save_gpr(ctx, rt, dest);

    /* Install the new nullification.  */
    cond_free(&ctx->null_cond);
    if (c) {
        ctx->null_cond = do_sed_cond(c, dest);
    }
    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus trans_extrw_sar(DisasContext *ctx, uint32_t insn,
                                  const DisasInsn *di)
{
    unsigned clen = extract32(insn, 0, 5);
    unsigned is_se = extract32(insn, 10, 1);
    unsigned c = extract32(insn, 13, 3);
    unsigned rt = extract32(insn, 16, 5);
    unsigned rr = extract32(insn, 21, 5);
    unsigned len = 32 - clen;
    TCGv dest, src, tmp;

    if (c) {
        nullify_over(ctx);
    }

    dest = dest_gpr(ctx, rt);
    src = load_gpr(ctx, rr);
    tmp = tcg_temp_new();

    /* Recall that SAR is using big-endian bit numbering.  */
    tcg_gen_xori_tl(tmp, cpu_sar, TARGET_LONG_BITS - 1);
    if (is_se) {
        tcg_gen_sar_tl(dest, src, tmp);
        tcg_gen_sextract_tl(dest, dest, 0, len);
    } else {
        tcg_gen_shr_tl(dest, src, tmp);
        tcg_gen_extract_tl(dest, dest, 0, len);
    }
    tcg_temp_free(tmp);
    save_gpr(ctx, rt, dest);

    /* Install the new nullification.  */
    cond_free(&ctx->null_cond);
    if (c) {
        ctx->null_cond = do_sed_cond(c, dest);
    }
    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus trans_extrw_imm(DisasContext *ctx, uint32_t insn,
                                  const DisasInsn *di)
{
    unsigned clen = extract32(insn, 0, 5);
    unsigned pos = extract32(insn, 5, 5);
    unsigned is_se = extract32(insn, 10, 1);
    unsigned c = extract32(insn, 13, 3);
    unsigned rt = extract32(insn, 16, 5);
    unsigned rr = extract32(insn, 21, 5);
    unsigned len = 32 - clen;
    unsigned cpos = 31 - pos;
    TCGv dest, src;

    if (c) {
        nullify_over(ctx);
    }

    dest = dest_gpr(ctx, rt);
    src = load_gpr(ctx, rr);
    if (is_se) {
        tcg_gen_sextract_tl(dest, src, cpos, len);
    } else {
        tcg_gen_extract_tl(dest, src, cpos, len);
    }
    save_gpr(ctx, rt, dest);

    /* Install the new nullification.  */
    cond_free(&ctx->null_cond);
    if (c) {
        ctx->null_cond = do_sed_cond(c, dest);
    }
    return nullify_end(ctx, NO_EXIT);
}

static const DisasInsn table_sh_ex[] = {
    { 0xd0000000u, 0xfc001fe0u, trans_shrpw_sar },
    { 0xd0000800u, 0xfc001c00u, trans_shrpw_imm },
    { 0xd0001000u, 0xfc001be0u, trans_extrw_sar },
    { 0xd0001800u, 0xfc001800u, trans_extrw_imm },
};

static ExitStatus trans_depw_imm_c(DisasContext *ctx, uint32_t insn,
                                   const DisasInsn *di)
{
    unsigned clen = extract32(insn, 0, 5);
    unsigned cpos = extract32(insn, 5, 5);
    unsigned nz = extract32(insn, 10, 1);
    unsigned c = extract32(insn, 13, 3);
    target_long val = low_sextract(insn, 16, 5);
    unsigned rt = extract32(insn, 21, 5);
    unsigned len = 32 - clen;
    target_long mask0, mask1;
    TCGv dest;

    if (c) {
        nullify_over(ctx);
    }
    if (cpos + len > 32) {
        len = 32 - cpos;
    }

    dest = dest_gpr(ctx, rt);
    mask0 = deposit64(0, cpos, len, val);
    mask1 = deposit64(-1, cpos, len, val);

    if (nz) {
        TCGv src = load_gpr(ctx, rt);
        if (mask1 != -1) {
            tcg_gen_andi_tl(dest, src, mask1);
            src = dest;
        }
        tcg_gen_ori_tl(dest, src, mask0);
    } else {
        tcg_gen_movi_tl(dest, mask0);
    }
    save_gpr(ctx, rt, dest);

    /* Install the new nullification.  */
    cond_free(&ctx->null_cond);
    if (c) {
        ctx->null_cond = do_sed_cond(c, dest);
    }
    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus trans_depw_imm(DisasContext *ctx, uint32_t insn,
                                 const DisasInsn *di)
{
    unsigned clen = extract32(insn, 0, 5);
    unsigned cpos = extract32(insn, 5, 5);
    unsigned nz = extract32(insn, 10, 1);
    unsigned c = extract32(insn, 13, 3);
    unsigned rr = extract32(insn, 16, 5);
    unsigned rt = extract32(insn, 21, 5);
    unsigned rs = nz ? rt : 0;
    unsigned len = 32 - clen;
    TCGv dest, val;

    if (c) {
        nullify_over(ctx);
    }
    if (cpos + len > 32) {
        len = 32 - cpos;
    }

    dest = dest_gpr(ctx, rt);
    val = load_gpr(ctx, rr);
    if (rs == 0) {
        tcg_gen_deposit_z_tl(dest, val, cpos, len);
    } else {
        tcg_gen_deposit_tl(dest, cpu_gr[rs], val, cpos, len);
    }
    save_gpr(ctx, rt, dest);

    /* Install the new nullification.  */
    cond_free(&ctx->null_cond);
    if (c) {
        ctx->null_cond = do_sed_cond(c, dest);
    }
    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus trans_depw_sar(DisasContext *ctx, uint32_t insn,
                                 const DisasInsn *di)
{
    unsigned clen = extract32(insn, 0, 5);
    unsigned nz = extract32(insn, 10, 1);
    unsigned i = extract32(insn, 12, 1);
    unsigned c = extract32(insn, 13, 3);
    unsigned rt = extract32(insn, 21, 5);
    unsigned rs = nz ? rt : 0;
    unsigned len = 32 - clen;
    TCGv val, mask, tmp, shift, dest;
    unsigned msb = 1U << (len - 1);

    if (c) {
        nullify_over(ctx);
    }

    if (i) {
        val = load_const(ctx, low_sextract(insn, 16, 5));
    } else {
        val = load_gpr(ctx, extract32(insn, 16, 5));
    }
    dest = dest_gpr(ctx, rt);
    shift = tcg_temp_new();
    tmp = tcg_temp_new();

    /* Convert big-endian bit numbering in SAR to left-shift.  */
    tcg_gen_xori_tl(shift, cpu_sar, TARGET_LONG_BITS - 1);

    mask = tcg_const_tl(msb + (msb - 1));
    tcg_gen_and_tl(tmp, val, mask);
    if (rs) {
        tcg_gen_shl_tl(mask, mask, shift);
        tcg_gen_shl_tl(tmp, tmp, shift);
        tcg_gen_andc_tl(dest, cpu_gr[rs], mask);
        tcg_gen_or_tl(dest, dest, tmp);
    } else {
        tcg_gen_shl_tl(dest, tmp, shift);
    }
    tcg_temp_free(shift);
    tcg_temp_free(mask);
    tcg_temp_free(tmp);
    save_gpr(ctx, rt, dest);

    /* Install the new nullification.  */
    cond_free(&ctx->null_cond);
    if (c) {
        ctx->null_cond = do_sed_cond(c, dest);
    }
    return nullify_end(ctx, NO_EXIT);
}

static const DisasInsn table_depw[] = {
    { 0xd4000000u, 0xfc000be0u, trans_depw_sar },
    { 0xd4000800u, 0xfc001800u, trans_depw_imm },
    { 0xd4001800u, 0xfc001800u, trans_depw_imm_c },
};

static ExitStatus trans_be(DisasContext *ctx, uint32_t insn, bool is_l)
{
    unsigned n = extract32(insn, 1, 1);
    unsigned b = extract32(insn, 21, 5);
    target_long disp = assemble_17(insn);

    /* unsigned s = low_uextract(insn, 13, 3); */
    /* ??? It seems like there should be a good way of using
       "be disp(sr2, r0)", the canonical gateway entry mechanism
       to our advantage.  But that appears to be inconvenient to
       manage along side branch delay slots.  Therefore we handle
       entry into the gateway page via absolute address.  */

    /* Since we don't implement spaces, just branch.  Do notice the special
       case of "be disp(*,r0)" using a direct branch to disp, so that we can
       goto_tb to the TB containing the syscall.  */
    if (b == 0) {
        return do_dbranch(ctx, disp, is_l ? 31 : 0, n);
    } else {
        TCGv tmp = get_temp(ctx);
        tcg_gen_addi_tl(tmp, load_gpr(ctx, b), disp);
        return do_ibranch(ctx, tmp, is_l ? 31 : 0, n);
    }
}

static ExitStatus trans_bl(DisasContext *ctx, uint32_t insn,
                           const DisasInsn *di)
{
    unsigned n = extract32(insn, 1, 1);
    unsigned link = extract32(insn, 21, 5);
    target_long disp = assemble_17(insn);

    return do_dbranch(ctx, iaoq_dest(ctx, disp), link, n);
}

static ExitStatus trans_bl_long(DisasContext *ctx, uint32_t insn,
                                const DisasInsn *di)
{
    unsigned n = extract32(insn, 1, 1);
    target_long disp = assemble_22(insn);

    return do_dbranch(ctx, iaoq_dest(ctx, disp), 2, n);
}

static ExitStatus trans_blr(DisasContext *ctx, uint32_t insn,
                            const DisasInsn *di)
{
    unsigned n = extract32(insn, 1, 1);
    unsigned rx = extract32(insn, 16, 5);
    unsigned link = extract32(insn, 21, 5);
    TCGv tmp = get_temp(ctx);

    tcg_gen_shli_tl(tmp, load_gpr(ctx, rx), 3);
    tcg_gen_addi_tl(tmp, tmp, ctx->iaoq_f + 8);
    return do_ibranch(ctx, tmp, link, n);
}

static ExitStatus trans_bv(DisasContext *ctx, uint32_t insn,
                           const DisasInsn *di)
{
    unsigned n = extract32(insn, 1, 1);
    unsigned rx = extract32(insn, 16, 5);
    unsigned rb = extract32(insn, 21, 5);
    TCGv dest;

    if (rx == 0) {
        dest = load_gpr(ctx, rb);
    } else {
        dest = get_temp(ctx);
        tcg_gen_shli_tl(dest, load_gpr(ctx, rx), 3);
        tcg_gen_add_tl(dest, dest, load_gpr(ctx, rb));
    }
    return do_ibranch(ctx, dest, 0, n);
}

static ExitStatus trans_bve(DisasContext *ctx, uint32_t insn,
                            const DisasInsn *di)
{
    unsigned n = extract32(insn, 1, 1);
    unsigned rb = extract32(insn, 21, 5);
    unsigned link = extract32(insn, 13, 1) ? 2 : 0;

    return do_ibranch(ctx, load_gpr(ctx, rb), link, n);
}

static const DisasInsn table_branch[] = {
    { 0xe8000000u, 0xfc006000u, trans_bl }, /* B,L and B,L,PUSH */
    { 0xe800a000u, 0xfc00e000u, trans_bl_long },
    { 0xe8004000u, 0xfc00fffdu, trans_blr },
    { 0xe800c000u, 0xfc00fffdu, trans_bv },
    { 0xe800d000u, 0xfc00dffcu, trans_bve },
};

static ExitStatus trans_fop_wew_0c(DisasContext *ctx, uint32_t insn,
                                   const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned ra = extract32(insn, 21, 5);
    return do_fop_wew(ctx, rt, ra, di->f_wew);
}

static ExitStatus trans_fop_wew_0e(DisasContext *ctx, uint32_t insn,
                                   const DisasInsn *di)
{
    unsigned rt = assemble_rt64(insn);
    unsigned ra = assemble_ra64(insn);
    return do_fop_wew(ctx, rt, ra, di->f_wew);
}

static ExitStatus trans_fop_ded(DisasContext *ctx, uint32_t insn,
                                const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned ra = extract32(insn, 21, 5);
    return do_fop_ded(ctx, rt, ra, di->f_ded);
}

static ExitStatus trans_fop_wed_0c(DisasContext *ctx, uint32_t insn,
                                   const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned ra = extract32(insn, 21, 5);
    return do_fop_wed(ctx, rt, ra, di->f_wed);
}

static ExitStatus trans_fop_wed_0e(DisasContext *ctx, uint32_t insn,
                                   const DisasInsn *di)
{
    unsigned rt = assemble_rt64(insn);
    unsigned ra = extract32(insn, 21, 5);
    return do_fop_wed(ctx, rt, ra, di->f_wed);
}

static ExitStatus trans_fop_dew_0c(DisasContext *ctx, uint32_t insn,
                                   const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned ra = extract32(insn, 21, 5);
    return do_fop_dew(ctx, rt, ra, di->f_dew);
}

static ExitStatus trans_fop_dew_0e(DisasContext *ctx, uint32_t insn,
                                   const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned ra = assemble_ra64(insn);
    return do_fop_dew(ctx, rt, ra, di->f_dew);
}

static ExitStatus trans_fop_weww_0c(DisasContext *ctx, uint32_t insn,
                                    const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned rb = extract32(insn, 16, 5);
    unsigned ra = extract32(insn, 21, 5);
    return do_fop_weww(ctx, rt, ra, rb, di->f_weww);
}

static ExitStatus trans_fop_weww_0e(DisasContext *ctx, uint32_t insn,
                                    const DisasInsn *di)
{
    unsigned rt = assemble_rt64(insn);
    unsigned rb = assemble_rb64(insn);
    unsigned ra = assemble_ra64(insn);
    return do_fop_weww(ctx, rt, ra, rb, di->f_weww);
}

static ExitStatus trans_fop_dedd(DisasContext *ctx, uint32_t insn,
                                 const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned rb = extract32(insn, 16, 5);
    unsigned ra = extract32(insn, 21, 5);
    return do_fop_dedd(ctx, rt, ra, rb, di->f_dedd);
}

static void gen_fcpy_s(TCGv_i32 dst, TCGv_env unused, TCGv_i32 src)
{
    tcg_gen_mov_i32(dst, src);
}

static void gen_fcpy_d(TCGv_i64 dst, TCGv_env unused, TCGv_i64 src)
{
    tcg_gen_mov_i64(dst, src);
}

static void gen_fabs_s(TCGv_i32 dst, TCGv_env unused, TCGv_i32 src)
{
    tcg_gen_andi_i32(dst, src, INT32_MAX);
}

static void gen_fabs_d(TCGv_i64 dst, TCGv_env unused, TCGv_i64 src)
{
    tcg_gen_andi_i64(dst, src, INT64_MAX);
}

static void gen_fneg_s(TCGv_i32 dst, TCGv_env unused, TCGv_i32 src)
{
    tcg_gen_xori_i32(dst, src, INT32_MIN);
}

static void gen_fneg_d(TCGv_i64 dst, TCGv_env unused, TCGv_i64 src)
{
    tcg_gen_xori_i64(dst, src, INT64_MIN);
}

static void gen_fnegabs_s(TCGv_i32 dst, TCGv_env unused, TCGv_i32 src)
{
    tcg_gen_ori_i32(dst, src, INT32_MIN);
}

static void gen_fnegabs_d(TCGv_i64 dst, TCGv_env unused, TCGv_i64 src)
{
    tcg_gen_ori_i64(dst, src, INT64_MIN);
}

static ExitStatus do_fcmp_s(DisasContext *ctx, unsigned ra, unsigned rb,
                            unsigned y, unsigned c)
{
    TCGv_i32 ta, tb, tc, ty;

    nullify_over(ctx);

    ta = load_frw0_i32(ra);
    tb = load_frw0_i32(rb);
    ty = tcg_const_i32(y);
    tc = tcg_const_i32(c);

    gen_helper_fcmp_s(cpu_env, ta, tb, ty, tc);

    tcg_temp_free_i32(ta);
    tcg_temp_free_i32(tb);
    tcg_temp_free_i32(ty);
    tcg_temp_free_i32(tc);

    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus trans_fcmp_s_0c(DisasContext *ctx, uint32_t insn,
                                  const DisasInsn *di)
{
    unsigned c = extract32(insn, 0, 5);
    unsigned y = extract32(insn, 13, 3);
    unsigned rb = extract32(insn, 16, 5);
    unsigned ra = extract32(insn, 21, 5);
    return do_fcmp_s(ctx, ra, rb, y, c);
}

static ExitStatus trans_fcmp_s_0e(DisasContext *ctx, uint32_t insn,
                                  const DisasInsn *di)
{
    unsigned c = extract32(insn, 0, 5);
    unsigned y = extract32(insn, 13, 3);
    unsigned rb = assemble_rb64(insn);
    unsigned ra = assemble_ra64(insn);
    return do_fcmp_s(ctx, ra, rb, y, c);
}

static ExitStatus trans_fcmp_d(DisasContext *ctx, uint32_t insn,
                               const DisasInsn *di)
{
    unsigned c = extract32(insn, 0, 5);
    unsigned y = extract32(insn, 13, 3);
    unsigned rb = extract32(insn, 16, 5);
    unsigned ra = extract32(insn, 21, 5);
    TCGv_i64 ta, tb;
    TCGv_i32 tc, ty;

    nullify_over(ctx);

    ta = load_frd0(ra);
    tb = load_frd0(rb);
    ty = tcg_const_i32(y);
    tc = tcg_const_i32(c);

    gen_helper_fcmp_d(cpu_env, ta, tb, ty, tc);

    tcg_temp_free_i64(ta);
    tcg_temp_free_i64(tb);
    tcg_temp_free_i32(ty);
    tcg_temp_free_i32(tc);

    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus trans_ftest_t(DisasContext *ctx, uint32_t insn,
                                const DisasInsn *di)
{
    unsigned y = extract32(insn, 13, 3);
    unsigned cbit = (y ^ 1) - 1;
    TCGv t;

    nullify_over(ctx);

    t = tcg_temp_new();
    tcg_gen_ld32u_tl(t, cpu_env, offsetof(CPUHPPAState, fr0_shadow));
    tcg_gen_extract_tl(t, t, 21 - cbit, 1);
    ctx->null_cond = cond_make_0(TCG_COND_NE, t);
    tcg_temp_free(t);

    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus trans_ftest_q(DisasContext *ctx, uint32_t insn,
                                const DisasInsn *di)
{
    unsigned c = extract32(insn, 0, 5);
    int mask;
    bool inv = false;
    TCGv t;

    nullify_over(ctx);

    t = tcg_temp_new();
    tcg_gen_ld32u_tl(t, cpu_env, offsetof(CPUHPPAState, fr0_shadow));

    switch (c) {
    case 0: /* simple */
        tcg_gen_andi_tl(t, t, 0x4000000);
        ctx->null_cond = cond_make_0(TCG_COND_NE, t);
        goto done;
    case 2: /* rej */
        inv = true;
        /* fallthru */
    case 1: /* acc */
        mask = 0x43ff800;
        break;
    case 6: /* rej8 */
        inv = true;
        /* fallthru */
    case 5: /* acc8 */
        mask = 0x43f8000;
        break;
    case 9: /* acc6 */
        mask = 0x43e0000;
        break;
    case 13: /* acc4 */
        mask = 0x4380000;
        break;
    case 17: /* acc2 */
        mask = 0x4200000;
        break;
    default:
        return gen_illegal(ctx);
    }
    if (inv) {
        TCGv c = load_const(ctx, mask);
        tcg_gen_or_tl(t, t, c);
        ctx->null_cond = cond_make(TCG_COND_EQ, t, c);
    } else {
        tcg_gen_andi_tl(t, t, mask);
        ctx->null_cond = cond_make_0(TCG_COND_EQ, t);
    }
 done:
    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus trans_xmpyu(DisasContext *ctx, uint32_t insn,
                              const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned rb = assemble_rb64(insn);
    unsigned ra = assemble_ra64(insn);
    TCGv_i64 a, b;

    nullify_over(ctx);

    a = load_frw0_i64(ra);
    b = load_frw0_i64(rb);
    tcg_gen_mul_i64(a, a, b);
    save_frd(rt, a);
    tcg_temp_free_i64(a);
    tcg_temp_free_i64(b);

    return nullify_end(ctx, NO_EXIT);
}

#define FOP_DED  trans_fop_ded, .f_ded
#define FOP_DEDD trans_fop_dedd, .f_dedd

#define FOP_WEW  trans_fop_wew_0c, .f_wew
#define FOP_DEW  trans_fop_dew_0c, .f_dew
#define FOP_WED  trans_fop_wed_0c, .f_wed
#define FOP_WEWW trans_fop_weww_0c, .f_weww

static const DisasInsn table_float_0c[] = {
    /* floating point class zero */
    { 0x30004000, 0xfc1fffe0, FOP_WEW = gen_fcpy_s },
    { 0x30006000, 0xfc1fffe0, FOP_WEW = gen_fabs_s },
    { 0x30008000, 0xfc1fffe0, FOP_WEW = gen_helper_fsqrt_s },
    { 0x3000a000, 0xfc1fffe0, FOP_WEW = gen_helper_frnd_s },
    { 0x3000c000, 0xfc1fffe0, FOP_WEW = gen_fneg_s },
    { 0x3000e000, 0xfc1fffe0, FOP_WEW = gen_fnegabs_s },

    { 0x30004800, 0xfc1fffe0, FOP_DED = gen_fcpy_d },
    { 0x30006800, 0xfc1fffe0, FOP_DED = gen_fabs_d },
    { 0x30008800, 0xfc1fffe0, FOP_DED = gen_helper_fsqrt_d },
    { 0x3000a800, 0xfc1fffe0, FOP_DED = gen_helper_frnd_d },
    { 0x3000c800, 0xfc1fffe0, FOP_DED = gen_fneg_d },
    { 0x3000e800, 0xfc1fffe0, FOP_DED = gen_fnegabs_d },

    /* floating point class three */
    { 0x30000600, 0xfc00ffe0, FOP_WEWW = gen_helper_fadd_s },
    { 0x30002600, 0xfc00ffe0, FOP_WEWW = gen_helper_fsub_s },
    { 0x30004600, 0xfc00ffe0, FOP_WEWW = gen_helper_fmpy_s },
    { 0x30006600, 0xfc00ffe0, FOP_WEWW = gen_helper_fdiv_s },

    { 0x30000e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fadd_d },
    { 0x30002e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fsub_d },
    { 0x30004e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fmpy_d },
    { 0x30006e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fdiv_d },

    /* floating point class one */
    /* float/float */
    { 0x30000a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_d_s },
    { 0x30002200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_s_d },
    /* int/float */
    { 0x30008200, 0xfc1fffe0, FOP_WEW = gen_helper_fcnv_w_s },
    { 0x30008a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_dw_s },
    { 0x3000a200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_w_d },
    { 0x3000aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_dw_d },
    /* float/int */
    { 0x30010200, 0xfc1fffe0, FOP_WEW = gen_helper_fcnv_s_w },
    { 0x30010a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_d_w },
    { 0x30012200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_s_dw },
    { 0x30012a00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_d_dw },
    /* float/int truncate */
    { 0x30018200, 0xfc1fffe0, FOP_WEW = gen_helper_fcnv_t_s_w },
    { 0x30018a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_t_d_w },
    { 0x3001a200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_t_s_dw },
    { 0x3001aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_t_d_dw },
    /* uint/float */
    { 0x30028200, 0xfc1fffe0, FOP_WEW = gen_helper_fcnv_uw_s },
    { 0x30028a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_udw_s },
    { 0x3002a200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_uw_d },
    { 0x3002aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_udw_d },
    /* float/uint */
    { 0x30030200, 0xfc1fffe0, FOP_WEW = gen_helper_fcnv_s_uw },
    { 0x30030a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_d_uw },
    { 0x30032200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_s_udw },
    { 0x30032a00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_d_udw },
    /* float/uint truncate */
    { 0x30038200, 0xfc1fffe0, FOP_WEW = gen_helper_fcnv_t_s_uw },
    { 0x30038a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_t_d_uw },
    { 0x3003a200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_t_s_udw },
    { 0x3003aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_t_d_udw },

    /* floating point class two */
    { 0x30000400, 0xfc001fe0, trans_fcmp_s_0c },
    { 0x30000c00, 0xfc001fe0, trans_fcmp_d },
    { 0x30002420, 0xffffffe0, trans_ftest_q },
    { 0x30000420, 0xffff1fff, trans_ftest_t },

    /* FID.  Note that ra == rt == 0, which via fcpy puts 0 into fr0.
       This is machine/revision == 0, which is reserved for simulator.  */
    { 0x30000000, 0xffffffff, FOP_WEW = gen_fcpy_s },
};

#undef FOP_WEW
#undef FOP_DEW
#undef FOP_WED
#undef FOP_WEWW
#define FOP_WEW  trans_fop_wew_0e, .f_wew
#define FOP_DEW  trans_fop_dew_0e, .f_dew
#define FOP_WED  trans_fop_wed_0e, .f_wed
#define FOP_WEWW trans_fop_weww_0e, .f_weww

static const DisasInsn table_float_0e[] = {
    /* floating point class zero */
    { 0x38004000, 0xfc1fff20, FOP_WEW = gen_fcpy_s },
    { 0x38006000, 0xfc1fff20, FOP_WEW = gen_fabs_s },
    { 0x38008000, 0xfc1fff20, FOP_WEW = gen_helper_fsqrt_s },
    { 0x3800a000, 0xfc1fff20, FOP_WEW = gen_helper_frnd_s },
    { 0x3800c000, 0xfc1fff20, FOP_WEW = gen_fneg_s },
    { 0x3800e000, 0xfc1fff20, FOP_WEW = gen_fnegabs_s },

    { 0x38004800, 0xfc1fffe0, FOP_DED = gen_fcpy_d },
    { 0x38006800, 0xfc1fffe0, FOP_DED = gen_fabs_d },
    { 0x38008800, 0xfc1fffe0, FOP_DED = gen_helper_fsqrt_d },
    { 0x3800a800, 0xfc1fffe0, FOP_DED = gen_helper_frnd_d },
    { 0x3800c800, 0xfc1fffe0, FOP_DED = gen_fneg_d },
    { 0x3800e800, 0xfc1fffe0, FOP_DED = gen_fnegabs_d },

    /* floating point class three */
    { 0x38000600, 0xfc00ef20, FOP_WEWW = gen_helper_fadd_s },
    { 0x38002600, 0xfc00ef20, FOP_WEWW = gen_helper_fsub_s },
    { 0x38004600, 0xfc00ef20, FOP_WEWW = gen_helper_fmpy_s },
    { 0x38006600, 0xfc00ef20, FOP_WEWW = gen_helper_fdiv_s },

    { 0x38000e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fadd_d },
    { 0x38002e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fsub_d },
    { 0x38004e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fmpy_d },
    { 0x38006e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fdiv_d },

    { 0x38004700, 0xfc00ef60, trans_xmpyu },

    /* floating point class one */
    /* float/float */
    { 0x38000a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_d_s },
    { 0x38002200, 0xfc1fffc0, FOP_DEW = gen_helper_fcnv_s_d },
    /* int/float */
    { 0x38008200, 0xfc1ffe60, FOP_WEW = gen_helper_fcnv_w_s },
    { 0x38008a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_dw_s },
    { 0x3800a200, 0xfc1fff60, FOP_DEW = gen_helper_fcnv_w_d },
    { 0x3800aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_dw_d },
    /* float/int */
    { 0x38010200, 0xfc1ffe60, FOP_WEW = gen_helper_fcnv_s_w },
    { 0x38010a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_d_w },
    { 0x38012200, 0xfc1fff60, FOP_DEW = gen_helper_fcnv_s_dw },
    { 0x38012a00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_d_dw },
    /* float/int truncate */
    { 0x38018200, 0xfc1ffe60, FOP_WEW = gen_helper_fcnv_t_s_w },
    { 0x38018a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_t_d_w },
    { 0x3801a200, 0xfc1fff60, FOP_DEW = gen_helper_fcnv_t_s_dw },
    { 0x3801aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_t_d_dw },
    /* uint/float */
    { 0x38028200, 0xfc1ffe60, FOP_WEW = gen_helper_fcnv_uw_s },
    { 0x38028a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_udw_s },
    { 0x3802a200, 0xfc1fff60, FOP_DEW = gen_helper_fcnv_uw_d },
    { 0x3802aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_udw_d },
    /* float/uint */
    { 0x38030200, 0xfc1ffe60, FOP_WEW = gen_helper_fcnv_s_uw },
    { 0x38030a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_d_uw },
    { 0x38032200, 0xfc1fff60, FOP_DEW = gen_helper_fcnv_s_udw },
    { 0x38032a00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_d_udw },
    /* float/uint truncate */
    { 0x38038200, 0xfc1ffe60, FOP_WEW = gen_helper_fcnv_t_s_uw },
    { 0x38038a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_t_d_uw },
    { 0x3803a200, 0xfc1fff60, FOP_DEW = gen_helper_fcnv_t_s_udw },
    { 0x3803aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_t_d_udw },

    /* floating point class two */
    { 0x38000400, 0xfc000f60, trans_fcmp_s_0e },
    { 0x38000c00, 0xfc001fe0, trans_fcmp_d },
};

#undef FOP_WEW
#undef FOP_DEW
#undef FOP_WED
#undef FOP_WEWW
#undef FOP_DED
#undef FOP_DEDD

/* Convert the fmpyadd single-precision register encodings to standard.  */
static inline int fmpyadd_s_reg(unsigned r)
{
    return (r & 16) * 2 + 16 + (r & 15);
}

static ExitStatus trans_fmpyadd(DisasContext *ctx, uint32_t insn, bool is_sub)
{
    unsigned tm = extract32(insn, 0, 5);
    unsigned f = extract32(insn, 5, 1);
    unsigned ra = extract32(insn, 6, 5);
    unsigned ta = extract32(insn, 11, 5);
    unsigned rm2 = extract32(insn, 16, 5);
    unsigned rm1 = extract32(insn, 21, 5);

    nullify_over(ctx);

    /* Independent multiply & add/sub, with undefined behaviour
       if outputs overlap inputs.  */
    if (f == 0) {
        tm = fmpyadd_s_reg(tm);
        ra = fmpyadd_s_reg(ra);
        ta = fmpyadd_s_reg(ta);
        rm2 = fmpyadd_s_reg(rm2);
        rm1 = fmpyadd_s_reg(rm1);
        do_fop_weww(ctx, tm, rm1, rm2, gen_helper_fmpy_s);
        do_fop_weww(ctx, ta, ta, ra,
                    is_sub ? gen_helper_fsub_s : gen_helper_fadd_s);
    } else {
        do_fop_dedd(ctx, tm, rm1, rm2, gen_helper_fmpy_d);
        do_fop_dedd(ctx, ta, ta, ra,
                    is_sub ? gen_helper_fsub_d : gen_helper_fadd_d);
    }

    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus trans_fmpyfadd_s(DisasContext *ctx, uint32_t insn,
                                   const DisasInsn *di)
{
    unsigned rt = assemble_rt64(insn);
    unsigned neg = extract32(insn, 5, 1);
    unsigned rm1 = assemble_ra64(insn);
    unsigned rm2 = assemble_rb64(insn);
    unsigned ra3 = assemble_rc64(insn);
    TCGv_i32 a, b, c;

    nullify_over(ctx);
    a = load_frw0_i32(rm1);
    b = load_frw0_i32(rm2);
    c = load_frw0_i32(ra3);

    if (neg) {
        gen_helper_fmpynfadd_s(a, cpu_env, a, b, c);
    } else {
        gen_helper_fmpyfadd_s(a, cpu_env, a, b, c);
    }

    tcg_temp_free_i32(b);
    tcg_temp_free_i32(c);
    save_frw_i32(rt, a);
    tcg_temp_free_i32(a);
    return nullify_end(ctx, NO_EXIT);
}

static ExitStatus trans_fmpyfadd_d(DisasContext *ctx, uint32_t insn,
                                   const DisasInsn *di)
{
    unsigned rt = extract32(insn, 0, 5);
    unsigned neg = extract32(insn, 5, 1);
    unsigned rm1 = extract32(insn, 21, 5);
    unsigned rm2 = extract32(insn, 16, 5);
    unsigned ra3 = assemble_rc64(insn);
    TCGv_i64 a, b, c;

    nullify_over(ctx);
    a = load_frd0(rm1);
    b = load_frd0(rm2);
    c = load_frd0(ra3);

    if (neg) {
        gen_helper_fmpynfadd_d(a, cpu_env, a, b, c);
    } else {
        gen_helper_fmpyfadd_d(a, cpu_env, a, b, c);
    }

    tcg_temp_free_i64(b);
    tcg_temp_free_i64(c);
    save_frd(rt, a);
    tcg_temp_free_i64(a);
    return nullify_end(ctx, NO_EXIT);
}

static const DisasInsn table_fp_fused[] = {
    { 0xb8000000u, 0xfc000800u, trans_fmpyfadd_s },
    { 0xb8000800u, 0xfc0019c0u, trans_fmpyfadd_d }
};

static ExitStatus translate_table_int(DisasContext *ctx, uint32_t insn,
                                      const DisasInsn table[], size_t n)
{
    size_t i;
    for (i = 0; i < n; ++i) {
        if ((insn & table[i].mask) == table[i].insn) {
            return table[i].trans(ctx, insn, &table[i]);
        }
    }
    return gen_illegal(ctx);
}

#define translate_table(ctx, insn, table) \
    translate_table_int(ctx, insn, table, ARRAY_SIZE(table))

static ExitStatus translate_one(DisasContext *ctx, uint32_t insn)
{
    uint32_t opc = extract32(insn, 26, 6);

    switch (opc) {
    case 0x00: /* system op */
        return translate_table(ctx, insn, table_system);
    case 0x01:
        return translate_table(ctx, insn, table_mem_mgmt);
    case 0x02:
        return translate_table(ctx, insn, table_arith_log);
    case 0x03:
        return translate_table(ctx, insn, table_index_mem);
    case 0x06:
        return trans_fmpyadd(ctx, insn, false);
    case 0x08:
        return trans_ldil(ctx, insn);
    case 0x09:
        return trans_copr_w(ctx, insn);
    case 0x0A:
        return trans_addil(ctx, insn);
    case 0x0B:
        return trans_copr_dw(ctx, insn);
    case 0x0C:
        return translate_table(ctx, insn, table_float_0c);
    case 0x0D:
        return trans_ldo(ctx, insn);
    case 0x0E:
        return translate_table(ctx, insn, table_float_0e);

    case 0x10:
        return trans_load(ctx, insn, false, MO_UB);
    case 0x11:
        return trans_load(ctx, insn, false, MO_TEUW);
    case 0x12:
        return trans_load(ctx, insn, false, MO_TEUL);
    case 0x13:
        return trans_load(ctx, insn, true, MO_TEUL);
    case 0x16:
        return trans_fload_mod(ctx, insn);
    case 0x17:
        return trans_load_w(ctx, insn);
    case 0x18:
        return trans_store(ctx, insn, false, MO_UB);
    case 0x19:
        return trans_store(ctx, insn, false, MO_TEUW);
    case 0x1A:
        return trans_store(ctx, insn, false, MO_TEUL);
    case 0x1B:
        return trans_store(ctx, insn, true, MO_TEUL);
    case 0x1E:
        return trans_fstore_mod(ctx, insn);
    case 0x1F:
        return trans_store_w(ctx, insn);

    case 0x20:
        return trans_cmpb(ctx, insn, true, false, false);
    case 0x21:
        return trans_cmpb(ctx, insn, true, true, false);
    case 0x22:
        return trans_cmpb(ctx, insn, false, false, false);
    case 0x23:
        return trans_cmpb(ctx, insn, false, true, false);
    case 0x24:
        return trans_cmpiclr(ctx, insn);
    case 0x25:
        return trans_subi(ctx, insn);
    case 0x26:
        return trans_fmpyadd(ctx, insn, true);
    case 0x27:
        return trans_cmpb(ctx, insn, true, false, true);
    case 0x28:
        return trans_addb(ctx, insn, true, false);
    case 0x29:
        return trans_addb(ctx, insn, true, true);
    case 0x2A:
        return trans_addb(ctx, insn, false, false);
    case 0x2B:
        return trans_addb(ctx, insn, false, true);
    case 0x2C:
    case 0x2D:
        return trans_addi(ctx, insn);
    case 0x2E:
        return translate_table(ctx, insn, table_fp_fused);
    case 0x2F:
        return trans_cmpb(ctx, insn, false, false, true);

    case 0x30:
    case 0x31:
        return trans_bb(ctx, insn);
    case 0x32:
        return trans_movb(ctx, insn, false);
    case 0x33:
        return trans_movb(ctx, insn, true);
    case 0x34:
        return translate_table(ctx, insn, table_sh_ex);
    case 0x35:
        return translate_table(ctx, insn, table_depw);
    case 0x38:
        return trans_be(ctx, insn, false);
    case 0x39:
        return trans_be(ctx, insn, true);
    case 0x3A:
        return translate_table(ctx, insn, table_branch);

    case 0x04: /* spopn */
    case 0x05: /* diag */
    case 0x0F: /* product specific */
        break;

    case 0x07: /* unassigned */
    case 0x15: /* unassigned */
    case 0x1D: /* unassigned */
    case 0x37: /* unassigned */
    case 0x3F: /* unassigned */
    default:
        break;
    }
    return gen_illegal(ctx);
}

void gen_intermediate_code(CPUHPPAState *env, struct TranslationBlock *tb)
{
    HPPACPU *cpu = hppa_env_get_cpu(env);
    CPUState *cs = CPU(cpu);
    DisasContext ctx;
    ExitStatus ret;
    int num_insns, max_insns, i;

    ctx.tb = tb;
    ctx.cs = cs;
    ctx.iaoq_f = tb->pc;
    ctx.iaoq_b = tb->cs_base;
    ctx.singlestep_enabled = cs->singlestep_enabled;

    ctx.ntemps = 0;
    for (i = 0; i < ARRAY_SIZE(ctx.temps); ++i) {
        TCGV_UNUSED(ctx.temps[i]);
    }

    /* Compute the maximum number of insns to execute, as bounded by
       (1) icount, (2) single-stepping, (3) branch delay slots, or
       (4) the number of insns remaining on the current page.  */
    max_insns = tb->cflags & CF_COUNT_MASK;
    if (max_insns == 0) {
        max_insns = CF_COUNT_MASK;
    }
    if (ctx.singlestep_enabled || singlestep) {
        max_insns = 1;
    } else if (max_insns > TCG_MAX_INSNS) {
        max_insns = TCG_MAX_INSNS;
    }

    num_insns = 0;
    gen_tb_start(tb);

    /* Seed the nullification status from PSW[N], as shown in TB->FLAGS.  */
    ctx.null_cond = cond_make_f();
    ctx.psw_n_nonzero = false;
    if (tb->flags & 1) {
        ctx.null_cond.c = TCG_COND_ALWAYS;
        ctx.psw_n_nonzero = true;
    }
    ctx.null_lab = NULL;

    do {
        tcg_gen_insn_start(ctx.iaoq_f, ctx.iaoq_b);
        num_insns++;

        if (unlikely(cpu_breakpoint_test(cs, ctx.iaoq_f, BP_ANY))) {
            ret = gen_excp(&ctx, EXCP_DEBUG);
            break;
        }
        if (num_insns == max_insns && (tb->cflags & CF_LAST_IO)) {
            gen_io_start();
        }

        if (ctx.iaoq_f < TARGET_PAGE_SIZE) {
            ret = do_page_zero(&ctx);
            assert(ret != NO_EXIT);
        } else {
            /* Always fetch the insn, even if nullified, so that we check
               the page permissions for execute.  */
            uint32_t insn = cpu_ldl_code(env, ctx.iaoq_f);

            /* Set up the IA queue for the next insn.
               This will be overwritten by a branch.  */
            if (ctx.iaoq_b == -1) {
                ctx.iaoq_n = -1;
                ctx.iaoq_n_var = get_temp(&ctx);
                tcg_gen_addi_tl(ctx.iaoq_n_var, cpu_iaoq_b, 4);
            } else {
                ctx.iaoq_n = ctx.iaoq_b + 4;
                TCGV_UNUSED(ctx.iaoq_n_var);
            }

            if (unlikely(ctx.null_cond.c == TCG_COND_ALWAYS)) {
                ctx.null_cond.c = TCG_COND_NEVER;
                ret = NO_EXIT;
            } else {
                ret = translate_one(&ctx, insn);
                assert(ctx.null_lab == NULL);
            }
        }

        for (i = 0; i < ctx.ntemps; ++i) {
            tcg_temp_free(ctx.temps[i]);
            TCGV_UNUSED(ctx.temps[i]);
        }
        ctx.ntemps = 0;

        /* If we see non-linear instructions, exhaust instruction count,
           or run out of buffer space, stop generation.  */
        /* ??? The non-linear instruction restriction is purely due to
           the debugging dump.  Otherwise we *could* follow unconditional
           branches within the same page.  */
        if (ret == NO_EXIT
            && (ctx.iaoq_b != ctx.iaoq_f + 4
                || num_insns >= max_insns
                || tcg_op_buf_full())) {
            if (ctx.null_cond.c == TCG_COND_NEVER
                || ctx.null_cond.c == TCG_COND_ALWAYS) {
                nullify_set(&ctx, ctx.null_cond.c == TCG_COND_ALWAYS);
                gen_goto_tb(&ctx, 0, ctx.iaoq_b, ctx.iaoq_n);
                ret = EXIT_GOTO_TB;
            } else {
                ret = EXIT_IAQ_N_STALE;
            }
        }

        ctx.iaoq_f = ctx.iaoq_b;
        ctx.iaoq_b = ctx.iaoq_n;
        if (ret == EXIT_NORETURN
            || ret == EXIT_GOTO_TB
            || ret == EXIT_IAQ_N_UPDATED) {
            break;
        }
        if (ctx.iaoq_f == -1) {
            tcg_gen_mov_tl(cpu_iaoq_f, cpu_iaoq_b);
            copy_iaoq_entry(cpu_iaoq_b, ctx.iaoq_n, ctx.iaoq_n_var);
            nullify_save(&ctx);
            ret = EXIT_IAQ_N_UPDATED;
            break;
        }
        if (ctx.iaoq_b == -1) {
            tcg_gen_mov_tl(cpu_iaoq_b, ctx.iaoq_n_var);
        }
    } while (ret == NO_EXIT);

    if (tb->cflags & CF_LAST_IO) {
        gen_io_end();
    }

    switch (ret) {
    case EXIT_GOTO_TB:
    case EXIT_NORETURN:
        break;
    case EXIT_IAQ_N_STALE:
        copy_iaoq_entry(cpu_iaoq_f, ctx.iaoq_f, cpu_iaoq_f);
        copy_iaoq_entry(cpu_iaoq_b, ctx.iaoq_b, cpu_iaoq_b);
        nullify_save(&ctx);
        /* FALLTHRU */
    case EXIT_IAQ_N_UPDATED:
        if (ctx.singlestep_enabled) {
            gen_excp_1(EXCP_DEBUG);
        } else {
            tcg_gen_exit_tb(0);
        }
        break;
    default:
        abort();
    }

    gen_tb_end(tb, num_insns);

    tb->size = num_insns * 4;
    tb->icount = num_insns;

#ifdef DEBUG_DISAS
    if (qemu_loglevel_mask(CPU_LOG_TB_IN_ASM)
        && qemu_log_in_addr_range(tb->pc)) {
        qemu_log_lock();
        switch (tb->pc) {
        case 0x00:
            qemu_log("IN:\n0x00000000:  (null)\n\n");
            break;
        case 0xb0:
            qemu_log("IN:\n0x000000b0:  light-weight-syscall\n\n");
            break;
        case 0xe0:
            qemu_log("IN:\n0x000000e0:  set-thread-pointer-syscall\n\n");
            break;
        case 0x100:
            qemu_log("IN:\n0x00000100:  syscall\n\n");
            break;
        default:
            qemu_log("IN: %s\n", lookup_symbol(tb->pc));
            log_target_disas(cs, tb->pc, tb->size, 1);
            qemu_log("\n");
            break;
        }
        qemu_log_unlock();
    }
#endif
}

void restore_state_to_opc(CPUHPPAState *env, TranslationBlock *tb,
                          target_ulong *data)
{
    env->iaoq_f = data[0];
    if (data[1] != -1) {
        env->iaoq_b = data[1];
    }
    /* Since we were executing the instruction at IAOQ_F, and took some
       sort of action that provoked the cpu_restore_state, we can infer
       that the instruction was not nullified.  */
    env->psw_n = 0;
}