File: jit-bisect.pl

package info (click to toggle)
moarvm 2020.12%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 18,652 kB
  • sloc: ansic: 268,178; perl: 8,186; python: 1,316; makefile: 768; sh: 287
file content (236 lines) | stat: -rwxr-xr-x 6,421 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#!/usr/bin/env perl
use strict;
use warnings;
use Getopt::Long;
use File::Spec;
use FindBin;
use lib File::Spec->catdir($FindBin::Bin, 'lib');
use timeout qw(run_timeout);

sub run_with {
    my ($command, $env, $timeout) = @_;
    my $status;
    {
        # simulate 'local' env vars, which doesn't really work with
        # child processes
        my %copy;
        while (my ($k,$v) = each %$env) {
            $copy{$k} = $ENV{$v};
            $ENV{$k} = $v;
        }
        if (defined $timeout) {
            $status = run_timeout $command, $timeout;
        } else {
            $status = system @$command;
        }
        while (my ($k,$v) = each %copy) {
            if (defined $v) {
                $ENV{$k} = $v;
            } else {
                delete $ENV{$k};
            }
        }
    }

    if ($status == -1) {
        local $" = ' ';
        die "Failed to start: `@$command`: $!";
    }

    return $status;
}

sub quietly(&) {
    my ($code) = @_;
    my ($error, @result);
    my ($dupout, $duperr);


    open $dupout, '>&', \*STDOUT;
    open $duperr, '>&', \*STDERR;
    close STDOUT;
    close STDERR;
    open STDOUT, '>', File::Spec->devnull;
    open STDERR, '>', File::Spec->devnull;

    eval {
        if (!defined wantarray) {
            $code->();
        } elsif (wantarray) {
            @result = $code->();
        } else {
            $result[0] = scalar $code->();
        }
        1;
    } or do {
        $error = $@ || $!;
    };

    close STDOUT;
    close STDERR;
    open STDOUT, '>&', $dupout;
    open STDERR, '>&', $duperr;
    close $dupout;
    close $duperr;

    die $error if $error;

    return wantarray ? @result : $result[0];
}

sub noisily(&) {
    my ($code) = @_;
    $code->();
}

sub bisect {
    my ($varname, $program, $env, $timeout) = @_;

    $env ||= {};
    printf STDERR ("Bisecting %s\n", $varname);
    if (%$env) {
        printf STDERR "Given:\n";
        printf STDERR "  %s=%s\n", $_, $env->{$_} for keys %$env;
    }

    my ($low, $high, $mid) = (0,1,0);
    my $status;

    do {
        printf STDERR "%s=%d", $varname, $high;
        $status = quietly {
            run_with($program, { %$env, $varname => $high }, $timeout);
        };
        if ($status == 0) {
            print STDERR "\tOK\n";
            ($low, $high) = ($high, $high * 2);
        } else {
            print STDERR "\tNOT OK\n";
        }
    } while ($status == 0);

     while (($high - $low) > 1) {
        $mid = int(($high + $low) / 2);
        printf STDERR "%s=%d", $varname, $mid;
        $status = quietly {
            run_with($program, { %$env, $varname => $mid }, $timeout);
        };
        if ($status == 0) {
            $low = $mid;
            print STDERR "\tOK\n";
        } else {
            $high = $mid;
            print STDERR "\tNOT OK\n";
        }
    }
    return $status ? $low : $mid;
}


my %OPTS = (
    verbose => 0,
    dump => 1,
    timeout => undef,
    spesh => 0,
    nodelay => 0,
);
GetOptions(\%OPTS, qw(verbose dump! timeout=i spesh nodelay)) or die "Could not get options";

my @command = @ARGV;
die 'Command is required' unless @command;

if ($OPTS{verbose}) {
    no warnings 'redefine';
    *quietly = \&noisily;
}
my $timeout = delete $OPTS{timeout};

# start with a clean slate
delete @ENV{qw(
    MVM_JIT_EXPR_DISABLE
    MVM_JIT_EXPR_LAST_FRAME
    MVM_JTI_EXPR_LAST_BB
    MVM_JIT_DISABLE
    MVM_SPESH_LIMIT
    MVM_SPESH_DISABLE
)};

# if we want to 'bisect' a spesh problem, also separate out the
# inline/osr flags
delete @ENV{qw(
    MVM_SPESH_INLINE_DISABLE
    MVM_SPESH_OSR_DISABLE
    MVM_SPESH_PEA_DISABLE
)} if $OPTS{spesh};
$ENV{MVM_SPESH_BLOCKING} = 1;
$ENV{MVM_SPESH_NODELAY} = 1 if exists $OPTS{nodelay};

# I find that the addition of the MVM_SPESH_LOG / MVM_JIT_LOG
# environment variable can sometimes change the spesh order of
# frames. So let's add it always so that when we run it for logging
# output, we don't accidentally log the wrong frame.
$ENV{$_} = File::Spec->devnull for qw(MVM_SPESH_LOG MVM_JIT_LOG);

quietly { run_with(\@command, {}, $timeout) } or do {
    die "This program is quite alright";
};
quietly {
    run_with(\@command, {
        ($OPTS{spesh} ? (MVM_SPESH_DISABLE => 1) : (MVM_JIT_EXPR_DISABLE => 1))
    }, $timeout)
} and do {
    die "This program cannot be bisected: $?";
};
printf STDERR "Checks OK, this program can be bisected\n";

if ($OPTS{spesh}) {
    # on the hypothesis that it is simpler to debug a spesh log
    # /without/ inlining or OSR, than with it, let's first try to
    # switch flags until we find a breaking combination
    my @flags = ({});
    for my $flag (qw(MVM_SPESH_OSR_DISABLE MVM_SPESH_INLINE_DISABLE MVM_SPESH_PEA_DISABLE MVM_JIT_DISABLE)) {
        @flags = map { $_, { %$_, $flag => 1 } } @flags; # this does not leave the flags in good left-to-right order
    }

    # Try to detect the right set of flags
    my $spesh_flags;
    for my $try_flags (sort { keys %$b <=> keys %$a } @flags) {
        quietly {
            run_with(\@command, $try_flags, $timeout);
        } and do {
            $spesh_flags = $try_flags;
            last;
        };
    }

    my $last_good_frame = bisect('MVM_SPESH_LIMIT', \@command, $spesh_flags, $timeout);
    printf STDERR ("SPESH Broken frame: %d.\n", $last_good_frame + 1);

    # alright, get a spesh diff
    my $log_file = sprintf("spesh-%04d.txt", $last_good_frame + 1);
    printf STDERR ("SPESH Acquiring log: %s\n", $log_file);
    run_with(\@command, {
        %$spesh_flags,
        MVM_SPESH_LOG => $log_file,
        MVM_SPESH_LIMIT => $last_good_frame + 1,
        MVM_JIT_DEBUG => 1,
    }, $timeout);
    print STDERR "Done\n";
} else {
    my $last_good_frame = bisect('MVM_JIT_EXPR_LAST_FRAME', \@command, {}, $timeout);
    my $last_good_block = bisect('MVM_JIT_EXPR_LAST_BB', \@command, {
        MVM_JIT_EXPR_LAST_FRAME => $last_good_frame + 1
    }, $timeout);
    printf STDERR ('JIT Broken Frame/BB: %d / %d'."\n", $last_good_frame + 1, $last_good_block + 1);

    run_with(\@command, {
        MVM_SPESH_LOG => sprintf('spesh-%04d-%04d.txt',
                                 $last_good_frame + 1, $last_good_block + 1),
        MVM_JIT_DEBUG => 1,
        MVM_SPESH_LIMIT => $last_good_frame + 1,
        MVM_JIT_EXPR_LAST_FRAME => $last_good_frame + 1,
        MVM_JIT_EXPR_LAST_BB => $last_good_block + 1,
    }) if $OPTS{dump};
}

__END__