GithubHelp home page GithubHelp logo

isle's People

Contributors

cfallin avatar fitzgen avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

isle's Issues

Clean up primitive declaration syntax

Annoying to repeat the primitive type name even though it is basically always the same in ISLE as it is in Rust.

Also, primitives are always external, so it makes sense that they would look similar to our extern enum declarations.

Maybe something like

(type i32 extern primitive) ;; or
(type int32_t (extern i32) primitive)

?

`islec`'s output is not deterministic

This will be an issue, as we want to commit results into the repo, and then check if they need to be rebuilt+recommitted in CI.

nick@hamilton :: (isle *+) :: ~/wasmtime
    $ islec ~/wasmtime/cranelift/codegen/src/clif.isle ~/wasmtime/cranelift/codegen/src/prelude.isle ~/wasmtime/cranelift/codegen/src/isa/x64/lower.isle > /tmp/a.rs

nick@hamilton :: (isle *+) :: ~/wasmtime
    $ islec ~/wasmtime/cranelift/codegen/src/clif.isle ~/wasmtime/cranelift/codegen/src/prelude.isle ~/wasmtime/cranelift/codegen/src/isa/x64/lower.isle > /tmp/b.rs

nick@hamilton :: (isle *+) :: ~/wasmtime
    $ diff /tmp/a.rs /tmp/b.rs
37a38,53
> // Generated as internal constructor for term xor.
> pub fn constructor_xor<C: Context>(ctx: &mut C, arg0: &OperandSize, arg1: &RegMemImm, arg2: WritableReg) -> Option<MachInst> {
>     let pattern0_0 = arg0;
>     let pattern1_0 = arg1;
>     let pattern2_0 = arg2;
>     // Rule at /home/nick/wasmtime/cranelift/codegen/src/isa/x64/lower.isle line 67.
>     let expr0_0 = AluRmiROpcode::Xor;
>     let expr1_0 = MachInst::AluRmiR {
>         size: pattern0_0.clone(),
>         op: expr0_0,
>         src: pattern1_0.clone(),
>         dst: pattern2_0,
>     };
>     return Some(expr1_0);
> }
> 
230,245d245
< }
< 
< // Generated as internal constructor for term xor.
< pub fn constructor_xor<C: Context>(ctx: &mut C, arg0: &OperandSize, arg1: &RegMemImm, arg2: WritableReg) -> Option<MachInst> {
<     let pattern0_0 = arg0;
<     let pattern1_0 = arg1;
<     let pattern2_0 = arg2;
<     // Rule at /home/nick/wasmtime/cranelift/codegen/src/isa/x64/lower.isle line 67.
<     let expr0_0 = AluRmiROpcode::Xor;
<     let expr1_0 = MachInst::AluRmiR {
<         size: pattern0_0.clone(),
<         op: expr0_0,
<         src: pattern1_0.clone(),
<         dst: pattern2_0,
<     };
<     return Some(expr1_0);

Introduce a `begin` / `do` / `progn` form

When we are emitting multiple instructions to already-chosen registers, we end up doing a bunch of stuff that could have some sugar:

;; `()` in Rust.
(type Unit (primitive Unit))

;; ...

(rule (lower (and (iconst (unwrap_u64_from_imm64 x))
                  (instruction_type $i128)))
      (let (_ Unit (emit (imm (OperandSize.Size64) x (get_output_reg_0))))
        (xor (OperandSize.Size64)
             (RegMemImm.Reg (writable_reg_to_reg (output_reg_1)))
             (output_reg_1))))

This would be nicer with (do ...) instead of (let (_ Unit ...) ...):

(rule (lower (and (iconst (unwrap_u64_from_imm64 x))
                  (instruction_type $i128)))
      (do (emit (imm (OperandSize.Size64) x (get_output_reg_0)))
          (xor (OperandSize.Size64)
               (RegMemImm.Reg (writable_reg_to_reg (output_reg_1)))
               (output_reg_1))))

Always call infallible extractors as late as possible

This input ISLE (plus a bunch of auto-generate extern types for clif stuff):

(decl writable_reg_to_reg (WritableReg) Reg)
(extern constructor writable_reg_to_reg writable_reg_to_reg)

;; Extract a `u8` from an `Imm64`.
(decl unwrap_u8_from_imm64 (u8) Imm64)
(extern extractor infallible unwrap_u8_from_imm64 unwrap_u8_from_imm64)

;; Extract a `u16` from an `Imm64`.
(decl unwrap_u16_from_imm64 (u16) Imm64)
(extern extractor infallible unwrap_u16_from_imm64 unwrap_u16_from_imm64)

;; Extract a `u32` from an `Imm64`.
(decl unwrap_u32_from_imm64 (u32) Imm64)
(extern extractor infallible unwrap_u32_from_imm64 unwrap_u32_from_imm64)

;; Extract a `u64` from an `Imm64`.
(decl unwrap_u64_from_imm64 (u64) Imm64)
(extern extractor infallible unwrap_u64_from_imm64 unwrap_u64_from_imm64)

;; Extract the type of an instruction.
(decl instruction_type (Type) InstructionData)
(extern extractor infallible instruction_type instruction_type)

;; Extractor to match an `iconst` of type `ir::types::I8` with an `i8`.
(decl iconst_i8 (u8) InstructionData)
(extractor
 (iconst_i8 x)
 (and (iconst (unwrap_u8_from_imm64 x))
      (instruction_type (I8))))

;; Extractor to match an `iconst` of type `ir::types::I16` with an `i16`.
(decl iconst_i16 (u16) InstructionData)
(extractor
 (iconst_i16 x)
 (and (iconst (unwrap_u16_from_imm64 x))
      (instruction_type (I16))))

;; Extractor to match an `iconst` of type `ir::types::I32` with an `i32`.
(decl iconst_i32 (u32) InstructionData)
(extractor
 (iconst_i32 x)
 (and (iconst (unwrap_u32_from_imm64 x))
      (instruction_type (I32))))

;; Extractor to match an `iconst` of type `ir::types::I64` with an `i64`.
(decl iconst_i64 (u64) InstructionData)
(extractor
 (iconst_i64 x)
 (and (iconst (unwrap_u8_from_imm64 x))
      (instruction_type (I64))))

;;;; Lowering Rules ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; The main lowering constructor term.
(decl lower (InstructionData) MachInst)

;; Rules for `iconst`.

(rule (lower (iconst_i8 0))
      (xor (OperandSize.Size32) (output_reg) (RegMemImm.Reg (writable_reg_to_reg (output_reg)))))
(rule (lower (iconst_i16 0))
      (xor (OperandSize.Size32) (output_reg) (RegMemImm.Reg (writable_reg_to_reg (output_reg)))))
(rule (lower (iconst_i32 0))
      (xor (OperandSize.Size32) (output_reg) (RegMemImm.Reg (writable_reg_to_reg (output_reg)))))
(rule (lower (iconst_i64 0))
      (xor (OperandSize.Size64) (output_reg) (RegMemImm.Reg (writable_reg_to_reg (output_reg)))))

generates this Rust code:

// Generated as internal constructor for term lower.
pub fn constructor_lower<C: Context>(ctx: &mut C, arg0: &InstructionData) -> Option<MachInst> {
    let pattern0_0 = arg0;
    if let &InstructionData::UnaryImm { opcode: ref pattern1_0, imm: pattern1_1 } = pattern0_0 {
        if let &Opcode::Iconst  = &pattern1_0 {
            let (pattern3_0,) = C::unwrap_u8_from_imm64(ctx, pattern1_1);
            if pattern3_0 == 0 {
                let (pattern5_0,) = C::instruction_type(ctx, pattern0_0);
                if let Some((,)) = C::match_i8(ctx, pattern5_0) {
                    // Rule at /home/nick/wasmtime/cranelift/codegen/src/isa/x64/lower.isle line 59.
                    let expr0_0 = OperandSize::Size32;
                    let expr1_0 = C::output_reg(ctx, );
                    let expr2_0 = C::output_reg(ctx, );
                    let expr3_0 = C::writable_reg_to_reg(ctx, expr2_0);
                    let expr4_0 = RegMemImm::Reg {
                        reg: expr3_0,
                    };
                    let expr5_0 = constructor_xor(ctx, expr0_0, expr1_0, expr4_0)?;
                    return Some(expr5_0);
                }
                if let Some((,)) = C::match_i64(ctx, pattern5_0) {
                    // Rule at /home/nick/wasmtime/cranelift/codegen/src/isa/x64/lower.isle line 65.
                    let expr0_0 = OperandSize::Size64;
                    let expr1_0 = C::output_reg(ctx, );
                    let expr2_0 = C::output_reg(ctx, );
                    let expr3_0 = C::writable_reg_to_reg(ctx, expr2_0);
                    let expr4_0 = RegMemImm::Reg {
                        reg: expr3_0,
                    };
                    let expr5_0 = constructor_xor(ctx, expr0_0, expr1_0, expr4_0)?;
                    return Some(expr5_0);
                }
            }
            let (pattern3_0,) = C::unwrap_u16_from_imm64(ctx, pattern1_1);
            if pattern3_0 == 0 {
                let (pattern5_0,) = C::instruction_type(ctx, pattern0_0);
                if let Some((,)) = C::match_i16(ctx, pattern5_0) {
                    // Rule at /home/nick/wasmtime/cranelift/codegen/src/isa/x64/lower.isle line 61.
                    let expr0_0 = OperandSize::Size32;
                    let expr1_0 = C::output_reg(ctx, );
                    let expr2_0 = C::output_reg(ctx, );
                    let expr3_0 = C::writable_reg_to_reg(ctx, expr2_0);
                    let expr4_0 = RegMemImm::Reg {
                        reg: expr3_0,
                    };
                    let expr5_0 = constructor_xor(ctx, expr0_0, expr1_0, expr4_0)?;
                    return Some(expr5_0);
                }
            }
            let (pattern3_0,) = C::unwrap_u32_from_imm64(ctx, pattern1_1);
            if pattern3_0 == 0 {
                let (pattern5_0,) = C::instruction_type(ctx, pattern0_0);
                if let Some((,)) = C::match_i32(ctx, pattern5_0) {
                    // Rule at /home/nick/wasmtime/cranelift/codegen/src/isa/x64/lower.isle line 63.
                    let expr0_0 = OperandSize::Size32;
                    let expr1_0 = C::output_reg(ctx, );
                    let expr2_0 = C::output_reg(ctx, );
                    let expr3_0 = C::writable_reg_to_reg(ctx, expr2_0);
                    let expr4_0 = RegMemImm::Reg {
                        reg: expr3_0,
                    };
                    let expr5_0 = constructor_xor(ctx, expr0_0, expr1_0, expr4_0)?;
                    return Some(expr5_0);
                }
            }
        }
    }
    return None;
}

Specifically, all the unwrap_uNN_from_imm64 extractor calls are infallible but are only used if matching all fallible extractors succeed. If you were hand-writing this, you would push them down into the most-nested ifs/matches/etc.

Of course, we can't always push them as deep as possible: a subsequent match operation might use their result as an input.

I think we should be able to do this kind of code motion pretty easily with a pass over the linear IR, before insertion into the term trie.

Sort linearized match ops to maximize trie prefix sharing

After linearizing rule patterns for a given term, but before we insert those linear match ops into the trie, we should do a custom topological sort of the linear match ops's data-flow graph that seeks to maximize prefix sharing in the eventual trie:

  • create a data-flow graph for each pattern
  • keep track of "emittable" match operations for each pattern, i.e. those operations whose operands are all already emitted so we can emit this operation if we choose
  • count which operations are most emittable across all patterns
  • select the operation with the highest count
  • for all patterns where this is an emittable operation:
    • emit this operation next
    • recurse
  • for all patterns where that is not an emittable operation:
    • choose the operation with the highest count among these remaining patterns
    • emit this operation next for these patterns
    • recurse
  • repeat previous until all patterns have been accounted for

This is greedy algorithm is not optimal, because we don't break ties between equally-emittable operations such that the final trie will have maximal prefix sharing. I can't think of an efficient way to do that right now. But I think this should get us very close to optimal in practice.

Allow redefining variables inside `let`s

This example redefines carry and hi_shifted:

(decl shl_i128 (ValueRegs Reg) ValueRegs)
(rule (shl_i128 src amt)
      ;; Unpack the registers that make up the 128-bit value being shifted.
      (let ((src_lo Reg (value_regs_get src 0))
            (src_hi Reg (value_regs_get src 1))
            ;; Do two 64-bit shifts.
            (lo_shifted Reg (shl $I64 src_lo (Imm8Reg.Reg amt)))
            (hi_shifted Reg (shl $I64 src_hi (Imm8Reg.Reg amt)))
            ;; `src_lo >> (64 - shift)` are the bits to carry over from the lo
            ;; into the hi.
            (carry Reg (shr $I64 src_lo (Imm8Reg.Reg (sub $I64 (imm $I64 64) (RegMemImm.Reg amt)))))
            (zero Reg (imm $I64 0))
            ;; Nullify the carry if we are shifting in by a multiple of 128.
            (carry Reg (with_flags_1 (test (OperandSize.Size64) (RegMemImm.Imm 127) amt)
                                     (cmove $I64 (CC.Z) (RegMem.Reg zero) carry)))
            ;; Add the carry into the high half.
            (hi_shifted Reg (or $I64 carry (RegMemImm.Reg hi_shifted))))
        ;; Combine the two shifted halves. However, if we are shifting by 64,
        ;; then the low bits are zero and the high bits are our low bits.
        (with_flags_2 (test (OperandSize.Size64) (RegMemImm.Imm 64) amt)
                      (cmove $I64 (CC.Z) (RegMem.Reg lo_shifted) zero)
                      (cmove $I64 (CC.Z) (RegMem.Reg hi_shifted) lo_shifted))))

Without the ability to redefine, we have to use names like carry2 and hi_shifted2 or something, like Erlang.

Missing cases for rules in generated code

Here we define three rules for imm:

  1. a general case
  2. a special case for i64s that fit in i32s
  3. a special case for 0
(type u64 (primitive u64))

(type Type extern (enum))
(extern const $I64 Type)

(type Reg extern (enum))

(decl foo () Reg)
(extern constructor foo foo)

(decl bar () Reg)
(extern constructor bar bar)

(decl qux () Reg)
(extern constructor qux qux)

(decl u64_fits_in_u32 (u64) u64)
(extern extractor u64_fits_in_u32 u64_fits_in_u32)

(decl imm (Type u64) Reg)

(rule (imm ty simm64)
      (foo))

(rule (imm $I64 (u64_fits_in_u32 x))
      (bar))

(rule (imm ty 0)
      (qux))

The generated code does not ever check for case (2):

// GENERATED BY ISLE. DO NOT EDIT!
//
// Generated automatically from the instruction-selection DSL code in:
// - isle_examples/fuzz.isle

#![allow(dead_code, unreachable_code, unreachable_patterns)]
#![allow(unused_imports, unused_variables, non_snake_case)]
#![allow(irrefutable_let_patterns)]

use super::*;  // Pulls in all external types.

/// Context during lowering: an implementation of this trait
/// must be provided with all external constructors and extractors.
/// A mutable borrow is passed along through all lowering logic.
pub trait Context {
    fn foo(&mut self, ) -> Reg;
    fn bar(&mut self, ) -> Reg;
    fn qux(&mut self, ) -> Reg;
    fn u64_fits_in_u32(&mut self, arg0: u64) -> Option<u64>;
}

// Generated as internal constructor for term imm.
pub fn constructor_imm<C: Context>(ctx: &mut C, arg0: &Type, arg1: u64) -> Option<Reg> {
    let pattern0_0 = arg0;
    let pattern1_0 = arg1;
    if pattern1_0 == 0 {
        // Rule at isle_examples/fuzz.isle line 1.
        let expr0_0 = C::qux(ctx, );
        return Some(expr0_0);
    }
    // Rule at isle_examples/fuzz.isle line 1.
    let expr0_0 = C::foo(ctx, );
    return Some(expr0_0);
}

Allow both constructors and extractors for the same term

Example:

(type i32 (primitive i32))

(type A (enum (A (a i32))))
(type B (enum (B (x i32) (y i32))))

;; `isub` has a constructor and extractor.
(decl isub (A A) B)
(rule (isub (A.A x) (A.A y) (B.B x y))
(extractor (isub (A.A x) (A.A y)) (B.B x y))

Also external:

;; ISLE representation of `[Value; 2]`.
(type ValueArray2 extern (enum))
(decl value_array_2 (Value Value) ValueArray2)
(extern extractor infallible value_array_2 unpack_value_array_2)
(extern constructor value_array_2 pack_value_array_2)

This will require an overhaul of TermKind.

Allow arbitrary patterns inside `let`s

This would allow us to, e.g., destructure things that return multiple values:

(let ((reg_low reg_high) (Reg Reg) (split_reg_low_high input_reg))
  ;; ...
  )

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.