Skip to content

Terrible code generation with a zillion bounds checks. #73512

@khuey

Description

@khuey

This is probably a duplicate of something.

In psychon/x11rb#491 we have a function that looks like:

    pub fn try_parse_request(header: RequestHeader, value: &[u8]) -> Result<Self, ParseError> {
        if header.minor_opcode != LATCH_LOCK_STATE_REQUEST {
            return Err(ParseError::ParseError);
        }
        let (device_spec, remaining) = DeviceSpec::try_parse(value)?;
        let (affect_mod_locks, remaining) = u8::try_parse(remaining)?;
        let (mod_locks, remaining) = u8::try_parse(remaining)?;
        let (lock_group, remaining) = bool::try_parse(remaining)?;
        let (group_lock, remaining) = u8::try_parse(remaining)?;
        let group_lock = group_lock.try_into()?;
        let (affect_mod_latches, remaining) = u8::try_parse(remaining)?;
        let remaining = remaining.get(1..).ok_or(ParseError::InsufficientData)?;
        let remaining = remaining.get(1..).ok_or(ParseError::InsufficientData)?;
        let (latch_group, remaining) = bool::try_parse(remaining)?;
        let (group_latch, remaining) = u16::try_parse(remaining)?;
        let _ = remaining;
        Ok(LatchLockStateRequest {
            device_spec,
            affect_mod_locks,
            mod_locks,
            lock_group,
            group_lock,
            affect_mod_latches,
            latch_group,
            group_latch,
        })
    }

Basically, it takes a byte stream in a wire format and parses it into an in-memory struct.

The generated assembly looks like

_ZN5x11rb8protocol3xkb21LatchLockStateRequest17try_parse_request17hb503842b081124b2E:
        movq    %rdi, %rax
        shrq    $40, %rsi
        cmpb    $5, %sil
        jne     .LBB4888_13
        cmpq    $2, %rcx
        jb      .LBB4888_16
        je      .LBB4888_16
        cmpq    $3, %rcx
        je      .LBB4888_16
        cmpq    $4, %rcx
        je      .LBB4888_16
        cmpb    $0, 4(%rdx)
        setne   %sil
        cmpq    $5, %rcx
        je      .LBB4888_16
        movb    5(%rdx), %dil
        cmpb    $4, %dil
        jae     .LBB4888_13
        cmpq    $6, %rcx
        je      .LBB4888_16
        cmpq    $7, %rcx
        je      .LBB4888_16
        cmpq    $8, %rcx
        je      .LBB4888_16
        cmpq    $9, %rcx
        je      .LBB4888_16
        andq    $-2, %rcx
        cmpq    $10, %rcx
        jne     .LBB4888_12
.LBB4888_16:
        movb    $0, 1(%rax)
        movb    $1, %cl
        movb    %cl, (%rax)
        retq
.LBB4888_13:
        movb    $1, 1(%rax)
        movb    $1, %cl
        movb    %cl, (%rax)
        retq
.LBB4888_12:
        movzwl  (%rdx), %ecx
        movb    2(%rdx), %r8b
        movb    3(%rdx), %r9b
        movb    6(%rdx), %r10b
        cmpb    $0, 9(%rdx)
        movzwl  10(%rdx), %edx
        movw    %cx, 2(%rax)
        movw    %dx, 4(%rax)
        movb    %r8b, 6(%rax)
        movb    %r9b, 7(%rax)
        movb    %sil, 8(%rax)
        movb    %dil, 9(%rax)
        movb    %r10b, 10(%rax)
        setne   11(%rax)
        xorl    %ecx, %ecx
        movb    %cl, (%rax)
        retq

LBB4888_16 is the ParseError::InsufficientData return and LBB4888_13 is the ParseError::ParseError return.

The compiler really should be able to consolidate the adjacent bounds checks. And although much smaller, not condensing the adjacent jb/jes is a comically easy missed optimization.

Meta

rustc --version --verbose:

rustc 1.46.0-nightly (e55d3f9c5 2020-06-18)
binary: rustc
commit-hash: e55d3f9c5213fe1a25366450127bdff67ad1eca2
commit-date: 2020-06-18
host: x86_64-unknown-linux-gnu
release: 1.46.0-nightly
LLVM version: 10.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.C-bugCategory: This is a bug.I-slowIssue: Problems and improvements with respect to performance of generated code.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions