<commit_before>#[link(name = "glgen",
       author = "Brendan Zabarauskas",
       vers = "0.1")];
#[comment = "OpenGL function loader generator."];

//! Requires libxml2
//!
//! This will be used to generate the loader from the [registry xml files]
//! (https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/):
//!
//! - `$ wget --no-check-certificate https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/gl.xml`
//! - `$ wget --no-check-certificate https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/glx.xml`
//! - `$ wget --no-check-certificate https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/wgl.xml`

extern mod extra;

use std::io;
use std::os;
use std::util;

use registry::*;

pub mod registry;
pub mod ty;

fn main() {
    match os::real_args() {
        [_, ref ns_str, ..args] => {
            let (path, ns) = match *ns_str {
                ~"gl" => (~"gl.xml", registry::Gl),
                ~"glx" => (~"glx.xml", registry::Glx),
                ~"wgl" => (~"wgl.xml", registry::Wgl),
                _ => fail!("Unexpected opengl namespace '%s'. Expected one of: gl, glx, or wgl.", *ns_str),
            };
            // Parse the XML registry.
            let reg = Registry::from_xml(
                io::file_reader(&Path(path))
                    .expect(fmt!("Could not read %s", path))
                    .read_c_str(),
                ns
            );
            parse_args(args, &reg, ns);
        }
        [_] => fail!("Error: expected an opengl namespace. Expected one of: gl, glx, or wgl."),
        [] => util::unreachable(),
    }
    // TODO: Use registry data to generate function loader.
}

fn parse_args(args: &[~str], reg: &Registry, ns: Ns) {
    match args {
        [~"ptr", .._] => {
            gen_ptr::write_loader(std::io::stdout(), reg, ns);
        }
        [~"struct", .._] => {
            gen_struct::write_loader(std::io::stdout(), reg, ns);
        }
        [ref flag] => {
            printfln!("Error: unexpected argument `%s`.", *flag);
        }
        [] => (),
    }
}

/// Print out the registry data for debugging.
fn print_registry(reg: &Registry) {
    printfln!("%?", reg);
}

/// Print out a list of all the types used in the registry.
fn print_ctys(reg: &Registry) {
    let tys = reg.get_tys();
    for ty in tys.iter() {
        printfln!("\"%s\"", *ty);
    }
}

/// Print out a list of all the types used in the registry, converted to their
/// Rust declaration syntax.
fn print_rtys(reg: &Registry) {
    let tys = reg.get_tys();
    for ty in tys.iter() {
        printfln!("\"%s\"", ty::to_rust_ty(*ty));
    }
}

pub mod gen {
    use super::ty;
    use registry::*;

    pub fn tab_str(n: uint) -> ~str {
        "    ".repeat(n)
    }

    pub fn enum_ident_str(enm: &Enum) -> ~str {
        if (enm.ident[0] as char).is_digit() {
            "_" + enm.ident
        } else {
            enm.ident.clone()
        }
    }

    pub fn enum_str(enm: &Enum, ty: &str) -> ~str {
        fmt!("pub static %s: %s = %s;",
            enum_ident_str(enm),
            ty, enm.value)
    }

    pub fn binding_ident_str(binding: &Binding, use_idents: bool) -> ~str {
        if use_idents {
            match binding.ident {
                ~"in" => ~"in_",
                ~"ref" => ~"ref_",
                ~"type" => ~"type_",
                ref ident => ident.clone(),
            }
        } else { ~"_" }
    }

    pub fn binding_str(binding: &Binding, use_idents: bool) -> ~str {
        fmt!("%s: %s",
            binding_ident_str(binding, use_idents),
            ty::to_rust_ty(binding.ty))
    }

    pub fn param_list_str(cmd: &Cmd, use_idents: bool) -> ~str {
        cmd.params.iter()
            .map(|b| binding_str(b, use_idents))
            .to_owned_vec()
            .connect(", ")
    }

    pub fn return_suffix_str(cmd: &Cmd) -> ~str {
        ty::to_return_suffix(
            ty::to_rust_ty(cmd.proto.ty)
        )
    }

    pub fn write_header(writer: @Writer, reg: &Registry, ns: Ns) {
        writer.write_line("use std::libc::*;");
        writer.write_line("use self::types::*;");
        writer.write_line("");
        writer.write_line("mod types {");
        writer.write_line(tab_str(1) + "use std::libc::*;");
        match ns {
            Gl => {
                for alias in ty::GL_ALIASES.iter()
                    { writer.write_line(tab_str(1) + *alias) }
            }
            Glx => {
                for alias in ty::X_ALIASES.iter()
                    { writer.write_line(tab_str(1) + *alias) }
                for alias in ty::GLX_ALIASES.iter()
                    { writer.write_line(tab_str(1) + *alias) }
            }
            Wgl => {
                for alias in ty::WIN_ALIASES.iter()
                    { writer.write_line(tab_str(1) + *alias) }
                for alias in ty::WGL_ALIASES.iter()
                    { writer.write_line(tab_str(1) + *alias) }
            }
        }
        writer.write_line("}");
        writer.write_line("");
        for ns in reg.enums.iter() {
            for def in ns.defs.iter() {
                writer.write_line(enum_str(def, "GLenum"));
            }
        }
    }
}

pub mod gen_ptr {
    use super::gen;
    use registry::*;

    pub fn static_mut_str(cmd: &Cmd) -> ~str {
        fmt!("pub static mut %s: extern \"C\" fn(%s)%s = failing::%s;",
            cmd.proto.ident,
            gen::param_list_str(cmd, true),
            gen::return_suffix_str(cmd),
            cmd.proto.ident)
    }

    pub fn is_loaded_str(cmd: &Cmd) -> ~str {
        fmt!("pub mod %s { pub static mut is_loaded: bool = false; }",
            cmd.proto.ident)
    }

    pub fn failing_fn_str(cmd: &Cmd) -> ~str {
        fmt!("extern \"C\" fn %s(%s)%s { fail!(\"%s was not loaded\") }",
            cmd.proto.ident,
            gen::param_list_str(cmd, false),
            gen::return_suffix_str(cmd),
            cmd.proto.ident)
    }

    pub fn load_statement_str(cmd: &Cmd) -> ~str {
        fmt!("match loadfn(\"%s\") { ptr if !ptr.is_null() => unsafe { %s = transmute(ptr); %s::is_loaded = true; }, _ => unsafe { %s::is_loaded = false; } }",
            cmd.proto.ident,
            cmd.proto.ident,
            cmd.proto.ident,
            cmd.proto.ident)
    }

    pub fn write_loader(writer: @Writer, reg: &Registry, ns: Ns) {
        gen::write_header(writer, reg, ns);
        writer.write_line("");

        // static muts for storing function pointers
        for def in reg.cmds[0].defs.iter() {
            writer.write_line(static_mut_str(def));
        }
        writer.write_line("");

        // static muts for storing the status of the function pointers
        for def in reg.cmds[0].defs.iter() {
            writer.write_line(is_loaded_str(def));
        }
        writer.write_line("");

        // failing functions to assign to the function pointers
        writer.write_line("mod failing {");
        for def in reg.cmds[0].defs.iter() {
            writer.write_line(gen::tab_str(1) + failing_fn_str(def));
        }
        writer.write_line("}");
        writer.write_line("");

        // loader function
        writer.write_line("/// Load each OpenGL symbol using a custom load function. This allows for the");
        writer.write_line("/// use of functions like `glfwGetProcAddress` or `SDL_GL_GetProcAddress`.");
        writer.write_line("///");
        writer.write_line("/// ~~~");
        writer.write_line("/// let gl = gl::load_with(glfw::get_proc_address);");
        writer.write_line("/// ~~~");
        writer.write_line("pub fn load_with(loadfn: &fn(symbol: &str) -> *c_void) {");
        writer.write_line("    use std::cast::transmute;");
        writer.write_line("");
        for def in reg.cmds[0].defs.iter() {
            writer.write_line(gen::tab_str(1) + load_statement_str(def));
        }
        writer.write_line("}");
    }
}

pub mod gen_struct {
    use super::gen;
    use registry::*;

    pub fn write_loader(writer: @Writer, reg: &Registry, ns: Ns) {
        gen::write_header(writer, reg, ns);
        writer.write_line("");
    }
}
<commit_msg>Finish ptr loader generator!<commit_after>#[link(name = "glgen",
       author = "Brendan Zabarauskas",
       vers = "0.1")];
#[comment = "OpenGL function loader generator."];

//! Requires libxml2
//!
//! This will be used to generate the loader from the [registry xml files]
//! (https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/):
//!
//! - `$ wget --no-check-certificate https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/gl.xml`
//! - `$ wget --no-check-certificate https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/glx.xml`
//! - `$ wget --no-check-certificate https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/wgl.xml`

extern mod extra;

use std::hashmap::HashMap;
use std::io;
use std::os;
use std::util;

use registry::*;

pub mod registry;
pub mod ty;

fn main() {
    match os::real_args() {
        [_, ref ns_str, ..args] => {
            let (path, ns) = match *ns_str {
                ~"gl" => (~"gl.xml", registry::Gl),
                ~"glx" => (~"glx.xml", registry::Glx),
                ~"wgl" => (~"wgl.xml", registry::Wgl),
                _ => fail!("Unexpected opengl namespace '%s'. Expected one of: gl, glx, or wgl.", *ns_str),
            };
            // Parse the XML registry.
            let reg = Registry::from_xml(
                io::file_reader(&Path(path))
                    .expect(fmt!("Could not read %s", path))
                    .read_c_str(),
                ns
            );
            parse_args(args, &reg, ns);
        }
        [_] => fail!("Error: expected an opengl namespace. Expected one of: gl, glx, or wgl."),
        [] => util::unreachable(),
    }
    // TODO: Use registry data to generate function loader.
}

fn parse_args(args: &[~str], reg: &Registry, ns: Ns) {
    match args {
        [~"ptr", .._] => {
            PtrGenerator::write(std::io::stdout(), reg, ns, 4);
        }
        [~"struct", .._] => {
            gen_struct::write_loader(std::io::stdout(), reg, ns);
        }
        [ref flag] => {
            printfln!("Error: unexpected argument `%s`.", *flag);
        }
        [] => (),
    }
}

/// Print out the registry data for debugging.
fn print_registry(reg: &Registry) {
    printfln!("%?", reg);
}

/// Print out a list of all the types used in the registry.
fn print_ctys(reg: &Registry) {
    let tys = reg.get_tys();
    for ty in tys.iter() {
        printfln!("\"%s\"", *ty);
    }
}

/// Print out a list of all the types used in the registry, converted to their
/// Rust declaration syntax.
fn print_rtys(reg: &Registry) {
    let tys = reg.get_tys();
    for ty in tys.iter() {
        printfln!("\"%s\"", ty::to_rust_ty(*ty));
    }
}

struct Generator<'self> {
    ns: Ns,
    writer: @Writer,
    registry: &'self Registry,
    indent: uint,
    tab_width: uint,
}

impl<'self> Generator<'self> {
    fn new<'a>(writer: @Writer, reg: &'a Registry, ns: Ns, tab_width: uint) -> Generator<'a> {
        Generator {
            ns: ns,
            writer: writer,
            registry: &'a *reg,
            indent: 0,
            tab_width: tab_width,
        }
    }

    fn incr_indent(&mut self) {
        self.indent += 1;
    }

    fn decr_indent(&mut self) {
        if self.indent > 0 { self.indent -= 1 }
    }

    fn write_indent(&self) {
        do (self.tab_width * self.indent).times {
            self.writer.write_char(' ');
        }
    }

    fn write_line(&self, s: &str) {
        self.write_indent();
        self.writer.write_line(s);
    }

    fn write_comment(&self, s: &str) {
        self.write_line("// " + s);
    }

    fn write_doc_comment(&self, s: &str) {
        self.write_line("/// " + s);
    }

    fn for_enums(&self, fn_ns: &fn(&EnumNs), fn_unseen: &fn(&EnumNs, &Enum), fn_seen: &fn(&EnumNs, &Enum)) {
        let mut seen = HashMap::new();
        for ns in self.registry.enums.iter() {
            fn_ns(ns);
            for def in ns.defs.iter() {
                match seen.find(&def.ident) {
                    Some(seen_ns) => { fn_seen(*seen_ns, def); loop; }
                    None => fn_unseen(ns, def),
                }
                seen.insert(def.ident.clone(), ns);
            }
        }
    }

    fn gen_enum_ident(enm: &Enum) -> ~str {
        if (enm.ident[0] as char).is_digit() {
            "_" + enm.ident
        } else {
            enm.ident.clone()
        }
    }

    fn write_enum(&self, enm: &Enum, ty: &str) {
        self.write_line(
            fmt!("pub static %s: %s = %s;",
                Generator::gen_enum_ident(enm),
                ty, enm.value)
        )
    }

    fn write_enums(&self) {
        self.for_enums(
            |_| (),
            |_, e| self.write_enum(e, "GLenum"),
            |_, _| ()
        )
    }

    fn for_cmds(&self, fn_ns: &fn(&CmdNs), fn_unseen: &fn(&CmdNs, &Cmd), fn_seen: &fn(&CmdNs, &Cmd)) {
        let mut seen = HashMap::new();
        for ns in self.registry.cmds.iter() {
            fn_ns(ns);
            for def in ns.defs.iter() {
                match seen.find(&def.proto.ident) {
                    Some(seen_ns) => { fn_seen(*seen_ns, def); loop; }
                    None => fn_unseen(ns, def),
                }
                seen.insert(def.proto.ident.clone(), ns);
            }
        }
    }

    fn gen_binding_ident(binding: &Binding, use_idents: bool) -> ~str {
        if use_idents {
            match binding.ident {
                ~"in" => ~"in_",
                ~"ref" => ~"ref_",
                ~"type" => ~"type_",
                ref ident => ident.clone(),
            }
        } else { ~"_" }
    }

    fn gen_binding(binding: &Binding, use_idents: bool) -> ~str {
        fmt!("%s: %s",
            Generator::gen_binding_ident(binding, use_idents),
            ty::to_rust_ty(binding.ty))
    }

    fn gen_param_list(cmd: &Cmd, use_idents: bool) -> ~str {
        cmd.params.iter()
            .map(|b| Generator::gen_binding(b, use_idents))
            .to_owned_vec()
            .connect(", ")
    }

    fn gen_return_suffix(cmd: &Cmd) -> ~str {
        ty::to_return_suffix(ty::to_rust_ty(cmd.proto.ty))
    }

    fn write_header(&mut self) {
        // imports
        self.write_line("use std::libc::*;");
        self.write_line("use self::types::*;");
        self.write_line("");

        // type aliases
        self.write_line("mod types {");
        self.incr_indent();
        self.write_line("use std::libc::*;");
        self.write_line("");
        match self.ns {
            Gl => {
                for alias in ty::GL_ALIASES.iter() { self.write_line(*alias) }
            }
            Glx => {
                for alias in ty::X_ALIASES.iter() { self.write_line(*alias) }
                for alias in ty::GLX_ALIASES.iter() { self.write_line(*alias) }
            }
            Wgl => {
                for alias in ty::WIN_ALIASES.iter() { self.write_line(*alias) }
                for alias in ty::WGL_ALIASES.iter() { self.write_line(*alias) }
            }
        }
        self.decr_indent();
        self.write_line("}");
        self.write_line("");

        // enums
        self.write_enums();
    }
}

struct PtrGenerator<'self>(Generator<'self>);

impl<'self> PtrGenerator<'self> {
    fn write_fns(&self) {
        self.for_cmds(
            |_| (),
            |_, c| self.write_line(
                fmt!("pub static mut %s: extern \"C\" fn(%s)%s = failing::%s;",
                    c.proto.ident,
                    Generator::gen_param_list(c, true),
                    Generator::gen_return_suffix(c),
                    c.proto.ident)
            ),
            |_, _| ()
        );
    }

    fn write_is_loaded_mods(&self) {
        self.for_cmds(
            |_| (),
            |_, c| self.write_line(fmt!("pub mod %s { pub static mut is_loaded: bool = false; }", c.proto.ident)),
            |_, _| ()
        );
    }

    fn write_failing_fns(&mut self) {
        self.write_line("mod failing {");
        self.incr_indent();
        self.write_line("use std::libc::*;");
        self.write_line("use super::types::*;");
        self.write_line("");
        self.for_cmds(
            |_| (),
            |_, c| self.write_line(
                fmt!("pub extern \"C\" fn %s(%s)%s { fail!(\"%s was not loaded\") }",
                    c.proto.ident,
                    Generator::gen_param_list(c, false),
                    Generator::gen_return_suffix(c),
                    c.proto.ident)
            ),
            |_, _| ()
        );
        self.decr_indent();
        self.write_line("}");
    }

    fn write_load_fn(&mut self) {
        self.write_line("/// Load each OpenGL symbol using a custom load function. This allows for the");
        self.write_line("/// use of functions like `glfwGetProcAddress` or `SDL_GL_GetProcAddress`.");
        self.write_line("///");
        self.write_line("/// ~~~");
        self.write_line("/// let gl = gl::load_with(glfw::get_proc_address);");
        self.write_line("/// ~~~");
        self.write_line("pub fn load_with(loadfn: &fn(symbol: &str) -> *c_void) {");
        self.incr_indent();
        self.write_line("use std::cast::transmute;");
        self.write_line("");
        self.for_cmds(
            |_| (),
            |_, c| self.write_line(
                fmt!(
                    "match loadfn(\"%s\") { \
                        ptr if !ptr.is_null() => unsafe { %s = transmute(ptr); %s::is_loaded = true; }, \
                        _ => unsafe { %s::is_loaded = false; } \
                    }",
                    c.proto.ident,
                    c.proto.ident,
                    c.proto.ident,
                    c.proto.ident
                )
            ),
            |_, _| ()
        );
        self.decr_indent();
        self.write_line("}");
    }

    fn write(writer: @Writer, reg: &Registry, ns: Ns, tab_width: uint) {
        let mut gen = PtrGenerator(
            Generator::new(writer, reg, ns, tab_width)
        );

        gen.write_header();
        gen.write_line("");

        // static muts for storing function pointers
        gen.write_fns();
        gen.write_line("");

        // static muts for storing the status of the function pointers
        gen.write_is_loaded_mods();
        gen.write_line("");

        // failing functions to assign to the function pointers
        gen.write_failing_fns();
        gen.write_line("");

        // loader function
        gen.write_load_fn();
        gen.write_line("");
    }
}

pub mod gen_struct {
    use registry::*;

    pub fn write_loader(_writer: @Writer, _reg: &Registry, _ns: Ns) {
        // gen::write_header(writer, reg, ns);
        // writer.write_line("");
    }
}
<|endoftext|>


<commit_before>use cranelift_codegen::cursor::{Cursor, FuncCursor};
use cranelift_codegen::ir::{Opcode, InstructionData, ValueDef};
use cranelift_codegen::ir::immediates::Offset32;
use cranelift_codegen::entity::SecondaryMap;

use crate::prelude::*;

pub(super) fn optimize_function(
    func: &mut Function,
    clif_comments: &mut crate::pretty_clif::CommentWriter,
    name: String, // FIXME remove
) {
    // Turn load and store into stack_load and stack_store when possible.
    let mut cursor = FuncCursor::new(func);
    while let Some(_ebb) = cursor.next_ebb() {
        while let Some(inst) = cursor.next_inst() {
            match cursor.func.dfg[inst] {
                InstructionData::Load { opcode: Opcode::Load, arg: addr, flags: _, offset } => {
                    if cursor.func.dfg.ctrl_typevar(inst) == types::I128 || cursor.func.dfg.ctrl_typevar(inst).is_vector() {
                        continue; // WORKAROUD: stack_load.i128 not yet implemented
                    }
                    if let Some((stack_slot, stack_addr_offset)) = try_get_stack_slot_and_offset_for_addr(cursor.func, addr) {
                        if let Some(combined_offset) = offset.try_add_i64(stack_addr_offset.into()) {
                            let ty = cursor.func.dfg.ctrl_typevar(inst);
                            cursor.func.dfg.replace(inst).stack_load(ty, stack_slot, combined_offset);
                        }
                    }
                }
                InstructionData::Store { opcode: Opcode::Store, args: [value, addr], flags: _, offset } => {
                    if cursor.func.dfg.ctrl_typevar(inst) == types::I128 || cursor.func.dfg.ctrl_typevar(inst).is_vector() {
                        continue; // WORKAROUND: stack_store.i128 not yet implemented
                    }
                    if let Some((stack_slot, stack_addr_offset)) = try_get_stack_slot_and_offset_for_addr(cursor.func, addr) {
                        if let Some(combined_offset) = offset.try_add_i64(stack_addr_offset.into()) {
                            cursor.func.dfg.replace(inst).stack_store(value, stack_slot, combined_offset);
                        }
                    }
                }
                _ => {}
            }
        }
    }

    // Record all stack_addr, stack_load and stack_store instructions. Also record all stack_addr
    // and stack_load insts whose result is used.
    let mut stack_addr_insts = SecondaryMap::new();
    let mut used_stack_addr_insts = SecondaryMap::new();
    let mut stack_load_insts = SecondaryMap::new();
    let mut used_stack_load_insts = SecondaryMap::new();
    let mut stack_store_insts = SecondaryMap::new();

    let mut cursor = FuncCursor::new(func);
    while let Some(_ebb) = cursor.next_ebb() {
        while let Some(inst) = cursor.next_inst() {
            match cursor.func.dfg[inst] {
                InstructionData::StackLoad {
                    opcode: Opcode::StackAddr,
                    stack_slot: _,
                    offset: _,
                } => {
                    stack_addr_insts[inst] = true;
                }
                InstructionData::StackLoad {
                    opcode: Opcode::StackLoad,
                    stack_slot: _,
                    offset: _,
                } => {
                    stack_load_insts[inst] = true;
                }
                InstructionData::StackStore {
                    opcode: Opcode::StackStore,
                    arg: _,
                    stack_slot: _,
                    offset: _,
                } => {
                    stack_store_insts[inst] = true;
                }
                _ => {}
            }

            for &arg in cursor.func.dfg.inst_args(inst) {
                if let ValueDef::Result(arg_origin, 0) = cursor.func.dfg.value_def(arg) {
                    match cursor.func.dfg[arg_origin].opcode() {
                        Opcode::StackAddr => used_stack_addr_insts[arg_origin] = true,
                        Opcode::StackLoad => used_stack_load_insts[arg_origin] = true,
                        _ => {}
                    }
                }
            }
        }
    }

    println!(
        "stack_addr: [{}] ([{}] used)\nstack_load: [{}] ([{}] used)\nstack_store: [{}]",
        bool_secondary_map_to_string(&stack_addr_insts),
        bool_secondary_map_to_string(&used_stack_addr_insts),
        bool_secondary_map_to_string(&stack_load_insts),
        bool_secondary_map_to_string(&used_stack_load_insts),
        bool_secondary_map_to_string(&stack_store_insts),
    );

    for inst in used_stack_addr_insts.keys().filter(|&inst| used_stack_addr_insts[inst]) {
        assert!(stack_addr_insts[inst]);
    }

    // Replace all unused stack_addr instructions with nop.
    for inst in stack_addr_insts.keys() {
        if stack_addr_insts[inst] && !used_stack_addr_insts[inst] {
            func.dfg.detach_results(inst);
            func.dfg.replace(inst).nop();
            stack_addr_insts[inst] = false;
        }
    }

    //println!("stack_addr (after): [{}]", bool_secondary_map_to_string(&stack_addr_insts));

    let mut stack_slot_usage_map: SecondaryMap<StackSlot, HashSet<Inst>> = SecondaryMap::new();
    for inst in stack_load_insts.keys().filter(|&inst| stack_load_insts[inst]) {
        match func.dfg[inst] {
            InstructionData::StackLoad {
                opcode: Opcode::StackLoad,
                stack_slot,
                offset: _,
            } => {
                stack_slot_usage_map[stack_slot].insert(inst);
            }
            ref data => unreachable!("{:?}", data),
        }
    }
    for inst in stack_store_insts.keys().filter(|&inst| stack_store_insts[inst]) {
        match func.dfg[inst] {
            InstructionData::StackStore {
                opcode: Opcode::StackStore,
                arg: _,
                stack_slot,
                offset: _,
            } => {
                stack_slot_usage_map[stack_slot].insert(inst);
            }
            ref data => unreachable!("{:?}", data),
        }
    }
    for inst in stack_addr_insts.keys().filter(|&inst| stack_addr_insts[inst]) {
        match func.dfg[inst] {
            InstructionData::StackLoad {
                opcode: Opcode::StackAddr,
                stack_slot,
                offset: _,
            } => {
                stack_slot_usage_map[stack_slot].insert(inst);
            }
            ref data => unreachable!("{:?}", data),
        }
    }

    println!("{:?}\n", stack_slot_usage_map);

    for (stack_slot, users) in stack_slot_usage_map.iter_mut() {
        let mut is_addr_leaked = false;
        let mut is_loaded = false;
        let mut is_stored = false;
        for &user in users.iter() {
            match func.dfg[user] {
                InstructionData::StackLoad {
                    opcode: Opcode::StackAddr,
                    stack_slot,
                    offset: _,
                } => {
                    is_addr_leaked = true;
                }
                InstructionData::StackLoad {
                    opcode: Opcode::StackLoad,
                    stack_slot,
                    offset: _,
                } => {
                    is_loaded = true;
                }
                InstructionData::StackStore {
                    opcode: Opcode::StackStore,
                    arg: _,
                    stack_slot,
                    offset: _,
                } => {
                    is_stored = true;
                }
                ref data => unreachable!("{:?}", data),
            }
        }

        if is_addr_leaked || (is_loaded && is_stored) {
            continue;
        }

        if is_loaded {
            println!("[{}] [BUG?] Reading uninitialized memory", name);
        } else {
            // Stored value never read; just remove reads.
            for &user in users.iter() {
                println!("[{}] Remove dead stack store {} of {}", name, user, stack_slot);
                func.dfg.replace(user).nop();
            }
        }
    }
}

fn try_get_stack_slot_and_offset_for_addr(func: &Function, addr: Value) -> Option<(StackSlot, Offset32)> {
    if let ValueDef::Result(addr_inst, 0) = func.dfg.value_def(addr) {
        if let InstructionData::StackLoad {
            opcode: Opcode::StackAddr,
            stack_slot,
            offset,
        } = func.dfg[addr_inst] {
            return Some((stack_slot, offset));
        }
    }
    None
}

fn bool_secondary_map_to_string<E>(map: &SecondaryMap<E, bool>) -> String
    where E: cranelift_codegen::entity::EntityRef + std::fmt::Display,
{
    map
        .keys()
        .filter_map(|inst| {
            // EntitySet::keys returns all possible entities until the last entity inserted.
            if map[inst] {
                Some(format!("{}", inst))
            } else {
                None
            }
        })
        .collect::<Vec<String>>()
        .join(", ")
}
<commit_msg>Outline combine_stack_addr_with_load_store and remove unused stack_load insts<commit_after>use cranelift_codegen::cursor::{Cursor, FuncCursor};
use cranelift_codegen::ir::{Opcode, InstructionData, ValueDef};
use cranelift_codegen::ir::immediates::Offset32;
use cranelift_codegen::entity::SecondaryMap;

use crate::prelude::*;

pub(super) fn optimize_function(
    func: &mut Function,
    clif_comments: &mut crate::pretty_clif::CommentWriter,
    name: String, // FIXME remove
) {
    combine_stack_addr_with_load_store(func);

    // Record all stack_addr, stack_load and stack_store instructions. Also record all stack_addr
    // and stack_load insts whose result is used.
    let mut stack_addr_insts = SecondaryMap::new();
    let mut used_stack_addr_insts = SecondaryMap::new();
    let mut stack_load_insts = SecondaryMap::new();
    let mut used_stack_load_insts = SecondaryMap::new();
    let mut stack_store_insts = SecondaryMap::new();

    let mut cursor = FuncCursor::new(func);
    while let Some(_ebb) = cursor.next_ebb() {
        while let Some(inst) = cursor.next_inst() {
            match cursor.func.dfg[inst] {
                InstructionData::StackLoad {
                    opcode: Opcode::StackAddr,
                    stack_slot: _,
                    offset: _,
                } => {
                    stack_addr_insts[inst] = true;
                }
                InstructionData::StackLoad {
                    opcode: Opcode::StackLoad,
                    stack_slot: _,
                    offset: _,
                } => {
                    stack_load_insts[inst] = true;
                }
                InstructionData::StackStore {
                    opcode: Opcode::StackStore,
                    arg: _,
                    stack_slot: _,
                    offset: _,
                } => {
                    stack_store_insts[inst] = true;
                }
                _ => {}
            }

            for &arg in cursor.func.dfg.inst_args(inst) {
                if let ValueDef::Result(arg_origin, 0) = cursor.func.dfg.value_def(arg) {
                    match cursor.func.dfg[arg_origin].opcode() {
                        Opcode::StackAddr => used_stack_addr_insts[arg_origin] = true,
                        Opcode::StackLoad => used_stack_load_insts[arg_origin] = true,
                        _ => {}
                    }
                }
            }
        }
    }

    println!(
        "stack_addr: [{}] ([{}] used)\nstack_load: [{}] ([{}] used)\nstack_store: [{}]",
        bool_secondary_map_to_string(&stack_addr_insts),
        bool_secondary_map_to_string(&used_stack_addr_insts),
        bool_secondary_map_to_string(&stack_load_insts),
        bool_secondary_map_to_string(&used_stack_load_insts),
        bool_secondary_map_to_string(&stack_store_insts),
    );

    for inst in used_stack_addr_insts.keys().filter(|&inst| used_stack_addr_insts[inst]) {
        assert!(stack_addr_insts[inst]);
    }

    // Replace all unused stack_addr instructions with nop.
    for inst in stack_addr_insts.keys() {
        if stack_addr_insts[inst] && !used_stack_addr_insts[inst] {
            func.dfg.detach_results(inst);
            func.dfg.replace(inst).nop();
            stack_addr_insts[inst] = false;
        }
    }

    for inst in used_stack_load_insts.keys().filter(|&inst| used_stack_load_insts[inst]) {
        assert!(stack_load_insts[inst]);
    }

    // Replace all unused stack_load instructions with nop.
    for inst in stack_load_insts.keys() {
        if stack_load_insts[inst] && !used_stack_load_insts[inst] {
            func.dfg.detach_results(inst);
            func.dfg.replace(inst).nop();
            stack_load_insts[inst] = false;
        }
    }

    //println!("stack_addr (after): [{}]", bool_secondary_map_to_string(&stack_addr_insts));

    let mut stack_slot_usage_map: SecondaryMap<StackSlot, HashSet<Inst>> = SecondaryMap::new();
    for inst in stack_load_insts.keys().filter(|&inst| stack_load_insts[inst]) {
        match func.dfg[inst] {
            InstructionData::StackLoad {
                opcode: Opcode::StackLoad,
                stack_slot,
                offset: _,
            } => {
                stack_slot_usage_map[stack_slot].insert(inst);
            }
            ref data => unreachable!("{:?}", data),
        }
    }
    for inst in stack_store_insts.keys().filter(|&inst| stack_store_insts[inst]) {
        match func.dfg[inst] {
            InstructionData::StackStore {
                opcode: Opcode::StackStore,
                arg: _,
                stack_slot,
                offset: _,
            } => {
                stack_slot_usage_map[stack_slot].insert(inst);
            }
            ref data => unreachable!("{:?}", data),
        }
    }
    for inst in stack_addr_insts.keys().filter(|&inst| stack_addr_insts[inst]) {
        match func.dfg[inst] {
            InstructionData::StackLoad {
                opcode: Opcode::StackAddr,
                stack_slot,
                offset: _,
            } => {
                stack_slot_usage_map[stack_slot].insert(inst);
            }
            ref data => unreachable!("{:?}", data),
        }
    }

    println!("{:?}\n", stack_slot_usage_map);

    for (stack_slot, users) in stack_slot_usage_map.iter_mut() {
        let mut is_addr_leaked = false;
        let mut is_loaded = false;
        let mut is_stored = false;
        for &user in users.iter() {
            match func.dfg[user] {
                InstructionData::StackLoad {
                    opcode: Opcode::StackAddr,
                    stack_slot,
                    offset: _,
                } => {
                    is_addr_leaked = true;
                }
                InstructionData::StackLoad {
                    opcode: Opcode::StackLoad,
                    stack_slot,
                    offset: _,
                } => {
                    is_loaded = true;
                }
                InstructionData::StackStore {
                    opcode: Opcode::StackStore,
                    arg: _,
                    stack_slot,
                    offset: _,
                } => {
                    is_stored = true;
                }
                ref data => unreachable!("{:?}", data),
            }
        }

        if is_addr_leaked || (is_loaded && is_stored) {
            continue;
        }

        if is_loaded {
            println!("[{}] [BUG?] Reading uninitialized memory", name);
        } else {
            // Stored value never read; just remove reads.
            for &user in users.iter() {
                println!("[{}] Remove dead stack store {} of {}", name, user, stack_slot);
                func.dfg.replace(user).nop();
            }
        }
    }
}

fn combine_stack_addr_with_load_store(func: &mut Function) {
    // Turn load and store into stack_load and stack_store when possible.
    let mut cursor = FuncCursor::new(func);
    while let Some(_ebb) = cursor.next_ebb() {
        while let Some(inst) = cursor.next_inst() {
            match cursor.func.dfg[inst] {
                InstructionData::Load { opcode: Opcode::Load, arg: addr, flags: _, offset } => {
                    if cursor.func.dfg.ctrl_typevar(inst) == types::I128 || cursor.func.dfg.ctrl_typevar(inst).is_vector() {
                        continue; // WORKAROUD: stack_load.i128 not yet implemented
                    }
                    if let Some((stack_slot, stack_addr_offset)) = try_get_stack_slot_and_offset_for_addr(cursor.func, addr) {
                        if let Some(combined_offset) = offset.try_add_i64(stack_addr_offset.into()) {
                            let ty = cursor.func.dfg.ctrl_typevar(inst);
                            cursor.func.dfg.replace(inst).stack_load(ty, stack_slot, combined_offset);
                        }
                    }
                }
                InstructionData::Store { opcode: Opcode::Store, args: [value, addr], flags: _, offset } => {
                    if cursor.func.dfg.ctrl_typevar(inst) == types::I128 || cursor.func.dfg.ctrl_typevar(inst).is_vector() {
                        continue; // WORKAROUND: stack_store.i128 not yet implemented
                    }
                    if let Some((stack_slot, stack_addr_offset)) = try_get_stack_slot_and_offset_for_addr(cursor.func, addr) {
                        if let Some(combined_offset) = offset.try_add_i64(stack_addr_offset.into()) {
                            cursor.func.dfg.replace(inst).stack_store(value, stack_slot, combined_offset);
                        }
                    }
                }
                _ => {}
            }
        }
    }
}

fn try_get_stack_slot_and_offset_for_addr(func: &Function, addr: Value) -> Option<(StackSlot, Offset32)> {
    if let ValueDef::Result(addr_inst, 0) = func.dfg.value_def(addr) {
        if let InstructionData::StackLoad {
            opcode: Opcode::StackAddr,
            stack_slot,
            offset,
        } = func.dfg[addr_inst] {
            return Some((stack_slot, offset));
        }
    }
    None
}

fn bool_secondary_map_to_string<E>(map: &SecondaryMap<E, bool>) -> String
    where E: cranelift_codegen::entity::EntityRef + std::fmt::Display,
{
    map
        .keys()
        .filter_map(|inst| {
            // EntitySet::keys returns all possible entities until the last entity inserted.
            if map[inst] {
                Some(format!("{}", inst))
            } else {
                None
            }
        })
        .collect::<Vec<String>>()
        .join(", ")
}
<|endoftext|>


<commit_before>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program 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
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io
use super::query;
use hyper::mime::Mime;
use iron::status::Status as IronStatus;
use iron::typemap::Key;
use mimir::rubber::Rubber;
use model;
use model::v1::*;
use params::{
    coord_param, dataset_param, get_param_array, paginate_param, shape_param, timeout_param,
    types_param,
};
use prometheus;
use prometheus::Encoder;
use rustless;
use rustless::server::header;
use rustless::{Api, Nesting};
use serde;
use serde_json;
use std::time;
use valico::json_dsl;

use navitia_model::objects::Coord;

const DEFAULT_LIMIT: u64 = 10u64;
const DEFAULT_OFFSET: u64 = 0u64;

struct Timer;
impl Key for Timer {
    type Value = prometheus::HistogramTimer;
}

lazy_static! {
    static ref HTTP_COUNTER: prometheus::CounterVec = register_counter_vec!(
        "bragi_http_requests_total",
        "Total number of HTTP requests made.",
        &["handler", "method", "status"]
    ).unwrap();
    static ref HTTP_REQ_HISTOGRAM: prometheus::HistogramVec = register_histogram_vec!(
        "bragi_http_request_duration_seconds",
        "The HTTP request latencies in seconds.",
        &["handler", "method"],
        prometheus::exponential_buckets(0.001, 1.5, 25).unwrap()
    ).unwrap();
}

fn add_distance(autocomp_resp: &mut model::Autocomplete, origin_coord: &Coord) {
    for feature in &mut autocomp_resp.features {
        if let ::geojson::Value::Point(p) = &feature.geometry.value {
            if let [mut lon, mut lat] = p.as_slice() {
                let feature_coord = Coord { lon, lat };
                feature.distance = Some(feature_coord.distance_to(&origin_coord) as u32);
            }
        }
    }
}

fn render<T>(
    mut client: rustless::Client,
    obj: T,
) -> Result<rustless::Client, rustless::ErrorResponse>
where
    T: serde::Serialize + model::v1::HasStatus,
{
    client.set_json_content_type();
    client.set_header(header::AccessControlAllowOrigin::Any);
    client.set_status(obj.status());
    client.text(serde_json::to_string(&obj).unwrap())
}

pub struct ApiEndPoint {
    pub es_cnx_string: String,
}

impl ApiEndPoint {
    pub fn root(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("", |endpoint| {
                endpoint.handle(|client, _params| {
                    let desc = EndPoint {
                        description: "autocomplete service".to_string(),
                    };
                    render(client, desc)
                })
            });

            api.error_formatter(|error, _media| {
                let err = if error.is::<rustless::errors::Validation>() {
                    let val_err = error.downcast::<rustless::errors::Validation>().unwrap();
                    // TODO better message, we shouldn't use {:?} but access the `path`
                    // and `detail` of all errrors in val_err.reason
                    CustomError {
                        short: "validation error".to_string(),
                        long: format!("invalid arguments {:?}", val_err.reason),
                        status: IronStatus::BadRequest,
                    }
                } else {
                    CustomError {
                        short: "bad_request".to_string(),
                        long: format!("bad request, error: {}", error),
                        status: IronStatus::BadRequest,
                    }
                };
                let mut resp = rustless::Response::from(
                    err.status,
                    Box::new(serde_json::to_string(&err).unwrap()),
                );
                resp.set_json_content_type();
                Some(resp)
            });

            api.before(|client, _params| {
                let method = client.endpoint.method.to_string();

                HTTP_REQ_HISTOGRAM
                    .get_metric_with(&labels!{
                        "handler" => client.endpoint.path.path.as_str(),
                        "method" => method.as_str(),
                    })
                    .map(|timer| {
                        client.ext.insert::<Timer>(timer.start_timer());
                    })
                    .unwrap_or_else(|err| {
                        error!("impossible to get HTTP_REQ_HISTOGRAM metrics";
                               "err" => err.to_string());
                    });
                Ok(())
            });

            api.after(|client, _params| {
                let method = client.endpoint.method.to_string();
                let code = client.status().to_string();

                HTTP_COUNTER
                    .get_metric_with(&labels!{
                        "handler" => client.endpoint.path.path.as_str(),
                        "method" => method.as_str(),
                        "status" => code.as_str(),
                    })
                    .map(|counter| counter.inc())
                    .unwrap_or_else(|err| {
                        error!("impossible to get HTTP_COUNTER metrics"; "err" => err.to_string());
                    });
                client
                    .ext
                    .remove::<Timer>()
                    .map(|timer| timer.observe_duration())
                    .unwrap_or_else(|| error!("impossible to get timers from typemap"));
                Ok(())
            });
            api.mount(self.v1());
        })
    }

    fn v1(&self) -> rustless::Api {
        Api::build(|api| {
            api.mount(self.status());
            api.mount(self.autocomplete());
            api.mount(self.features());
            api.mount(self.reverse());
            api.mount(self.metrics());
        })
    }

    fn status(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("status", |endpoint| {
                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, _params| {
                    let status = Status {
                        version: env!("CARGO_PKG_VERSION").to_string(),
                        es: cnx.to_string(),
                        status: "good".to_string(),
                    };
                    render(client, status)
                })
            });
        })
    }

    fn metrics(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("metrics", |endpoint| {
                endpoint.handle(move |mut client, _params| {
                    let encoder = prometheus::TextEncoder::new();
                    let metric_familys = prometheus::gather();
                    let mut buffer = vec![];
                    encoder.encode(&metric_familys, &mut buffer).unwrap();
                    client.set_content_type(encoder.format_type().parse::<Mime>().unwrap());
                    client.text(String::from_utf8(buffer).unwrap())
                })
            });
        })
    }

    fn reverse(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("reverse", |endpoint| {
                endpoint.params(|params| {
                    coord_param(params, false);
                    timeout_param(params);
                });
                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let coord = ::mimir::Coord::new(
                        params.find("lon").and_then(|p| p.as_f64()).unwrap(),
                        params.find("lat").and_then(|p| p.as_f64()).unwrap(),
                    );
                    let timeout = params
                        .find("timeout")
                        .and_then(|v| Some(time::Duration::from_millis(v.as_u64().unwrap())));
                    let mut rubber = Rubber::new(&cnx);
                    rubber.set_read_timeout(timeout);
                    let model_autocomplete =
                        rubber.get_address(&coord).map_err(model::BragiError::from);

                    let response = model::v1::AutocompleteResponse::from(model_autocomplete);
                    render(client, response)
                })
            });
        })
    }

    fn features(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("features/:id", |endpoint| {
                endpoint.params(|params| {
                    params.opt_typed("id", json_dsl::string());
                    dataset_param(params);
                    timeout_param(params);
                });

                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let id = params.find("id").unwrap().as_str().unwrap();
                    let pt_datasets = get_param_array(params, "pt_dataset");
                    let all_data = params
                        .find("_all_data")
                        .map_or(false, |val| val.as_bool().unwrap());
                    let timeout = params
                        .find("timeout")
                        .and_then(|v| Some(time::Duration::from_millis(v.as_u64().unwrap())));
                    let features = query::features(&pt_datasets, all_data, &cnx, &id, timeout);
                    let response = model::v1::AutocompleteResponse::from(features);
                    render(client, response)
                })
            });
        })
    }

    fn autocomplete(&self) -> rustless::Api {
        Api::build(|api| {
            api.post("autocomplete", |endpoint| {
                endpoint.params(|params| {
                    params.opt_typed("q", json_dsl::string());
                    dataset_param(params);
                    paginate_param(params);
                    shape_param(params);
                    types_param(params);
                    timeout_param(params);
                });

                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let q = params
                        .find("q")
                        .and_then(|val| val.as_str())
                        .unwrap_or("")
                        .to_string();
                    let pt_datasets = get_param_array(params, "pt_dataset");
                    let all_data = params
                        .find("_all_data")
                        .map_or(false, |val| val.as_bool().unwrap());
                    let offset = params
                        .find("offset")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_OFFSET);
                    let limit = params
                        .find("limit")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_LIMIT);
                    let geometry = params.find_path(&["shape", "geometry"]).unwrap();
                    let coordinates = geometry
                        .find_path(&["coordinates"])
                        .unwrap()
                        .as_array()
                        .unwrap();
                    let mut shape = Vec::new();
                    for ar in coordinates[0].as_array().unwrap() {
                        // (Lat, Lon)
                        shape.push((
                            ar.as_array().unwrap()[1].as_f64().unwrap(),
                            ar.as_array().unwrap()[0].as_f64().unwrap(),
                        ));
                    }
                    let types = get_param_array(params, "type");
                    let timeout = params
                        .find("timeout")
                        .and_then(|v| Some(time::Duration::from_millis(v.as_u64().unwrap())));
                    let model_autocomplete = query::autocomplete(
                        &q,
                        &pt_datasets,
                        all_data,
                        offset,
                        limit,
                        None,
                        &cnx,
                        Some(shape),
                        &types,
                        timeout,
                    );
                    let response = model::v1::AutocompleteResponse::from(model_autocomplete);
                    render(client, response)
                })
            });
            api.get("autocomplete", |endpoint| {
                endpoint.params(|params| {
                    params.opt_typed("q", json_dsl::string());
                    dataset_param(params);
                    paginate_param(params);
                    coord_param(params, true);
                    types_param(params);
                    timeout_param(params);
                });
                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let q = params
                        .find("q")
                        .and_then(|val| val.as_str())
                        .unwrap_or("")
                        .to_string();
                    let pt_datasets = get_param_array(params, "pt_dataset");
                    let all_data = params
                        .find("_all_data")
                        .map_or(false, |val| val.as_bool().unwrap());
                    let offset = params
                        .find("offset")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_OFFSET);
                    let limit = params
                        .find("limit")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_LIMIT);
                    let lon = params.find("lon").and_then(|p| p.as_f64());
                    let lat = params.find("lat").and_then(|p| p.as_f64());
                    // we have already checked that if there is a lon, lat
                    // is not None so we can unwrap
                    let coord = lon.and_then(|lon| {
                        Some(Coord {
                            lon: lon,
                            lat: lat.unwrap(),
                        })
                    });

                    let types = get_param_array(params, "type");
                    let timeout = params
                        .find("timeout")
                        .and_then(|v| Some(time::Duration::from_millis(v.as_u64().unwrap())));

                    let model_autocomplete = query::autocomplete(
                        &q,
                        &pt_datasets,
                        all_data,
                        offset,
                        limit,
                        coord,
                        &cnx,
                        None,
                        &types,
                        timeout,
                    );

                    let mut response = model::v1::AutocompleteResponse::from(model_autocomplete);

                    // Optional : add distance for each feature (in meters)
                    use model::v1::AutocompleteResponse::Autocomplete;
                    if let (Some(coord), Autocomplete(autocomplete_resp)) = (&coord, &mut response)
                    {
                        add_distance(autocomplete_resp, coord);
                    }

                    render(client, response)
                })
            });
        })
    }
}
<commit_msg>update after review<commit_after>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program 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
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io
use super::query;
use hyper::mime::Mime;
use iron::status::Status as IronStatus;
use iron::typemap::Key;
use mimir::rubber::Rubber;
use model;
use model::v1::*;
use params::{
    coord_param, dataset_param, get_param_array, paginate_param, shape_param, timeout_param,
    types_param,
};
use prometheus;
use prometheus::Encoder;
use rustless;
use rustless::server::header;
use rustless::{Api, Nesting};
use serde;
use serde_json;
use std::time;
use valico::json_dsl;

use navitia_model::objects::Coord;

const DEFAULT_LIMIT: u64 = 10u64;
const DEFAULT_OFFSET: u64 = 0u64;

struct Timer;
impl Key for Timer {
    type Value = prometheus::HistogramTimer;
}

lazy_static! {
    static ref HTTP_COUNTER: prometheus::CounterVec = register_counter_vec!(
        "bragi_http_requests_total",
        "Total number of HTTP requests made.",
        &["handler", "method", "status"]
    ).unwrap();
    static ref HTTP_REQ_HISTOGRAM: prometheus::HistogramVec = register_histogram_vec!(
        "bragi_http_request_duration_seconds",
        "The HTTP request latencies in seconds.",
        &["handler", "method"],
        prometheus::exponential_buckets(0.001, 1.5, 25).unwrap()
    ).unwrap();
}

fn parse_timeout(params: &rustless::json::JsonValue) -> Option<time::Duration> {
    params
        .find("timeout")
        .and_then(|v| v.as_u64())
        .map(time::Duration::from_millis)
}

fn add_distance(autocomp_resp: &mut model::Autocomplete, origin_coord: &Coord) {
    for feature in &mut autocomp_resp.features {
        if let ::geojson::Value::Point(p) = &feature.geometry.value {
            if let [mut lon, mut lat] = p.as_slice() {
                let feature_coord = Coord { lon, lat };
                feature.distance = Some(feature_coord.distance_to(&origin_coord) as u32);
            }
        }
    }
}

fn render<T>(
    mut client: rustless::Client,
    obj: T,
) -> Result<rustless::Client, rustless::ErrorResponse>
where
    T: serde::Serialize + model::v1::HasStatus,
{
    client.set_json_content_type();
    client.set_header(header::AccessControlAllowOrigin::Any);
    client.set_status(obj.status());
    client.text(serde_json::to_string(&obj).unwrap())
}

pub struct ApiEndPoint {
    pub es_cnx_string: String,
}

impl ApiEndPoint {
    pub fn root(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("", |endpoint| {
                endpoint.handle(|client, _params| {
                    let desc = EndPoint {
                        description: "autocomplete service".to_string(),
                    };
                    render(client, desc)
                })
            });

            api.error_formatter(|error, _media| {
                let err = if error.is::<rustless::errors::Validation>() {
                    let val_err = error.downcast::<rustless::errors::Validation>().unwrap();
                    // TODO better message, we shouldn't use {:?} but access the `path`
                    // and `detail` of all errrors in val_err.reason
                    CustomError {
                        short: "validation error".to_string(),
                        long: format!("invalid arguments {:?}", val_err.reason),
                        status: IronStatus::BadRequest,
                    }
                } else {
                    CustomError {
                        short: "bad_request".to_string(),
                        long: format!("bad request, error: {}", error),
                        status: IronStatus::BadRequest,
                    }
                };
                let mut resp = rustless::Response::from(
                    err.status,
                    Box::new(serde_json::to_string(&err).unwrap()),
                );
                resp.set_json_content_type();
                Some(resp)
            });

            api.before(|client, _params| {
                let method = client.endpoint.method.to_string();

                HTTP_REQ_HISTOGRAM
                    .get_metric_with(&labels!{
                        "handler" => client.endpoint.path.path.as_str(),
                        "method" => method.as_str(),
                    })
                    .map(|timer| {
                        client.ext.insert::<Timer>(timer.start_timer());
                    })
                    .unwrap_or_else(|err| {
                        error!("impossible to get HTTP_REQ_HISTOGRAM metrics";
                               "err" => err.to_string());
                    });
                Ok(())
            });

            api.after(|client, _params| {
                let method = client.endpoint.method.to_string();
                let code = client.status().to_string();

                HTTP_COUNTER
                    .get_metric_with(&labels!{
                        "handler" => client.endpoint.path.path.as_str(),
                        "method" => method.as_str(),
                        "status" => code.as_str(),
                    })
                    .map(|counter| counter.inc())
                    .unwrap_or_else(|err| {
                        error!("impossible to get HTTP_COUNTER metrics"; "err" => err.to_string());
                    });
                client
                    .ext
                    .remove::<Timer>()
                    .map(|timer| timer.observe_duration())
                    .unwrap_or_else(|| error!("impossible to get timers from typemap"));
                Ok(())
            });
            api.mount(self.v1());
        })
    }

    fn v1(&self) -> rustless::Api {
        Api::build(|api| {
            api.mount(self.status());
            api.mount(self.autocomplete());
            api.mount(self.features());
            api.mount(self.reverse());
            api.mount(self.metrics());
        })
    }

    fn status(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("status", |endpoint| {
                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, _params| {
                    let status = Status {
                        version: env!("CARGO_PKG_VERSION").to_string(),
                        es: cnx.to_string(),
                        status: "good".to_string(),
                    };
                    render(client, status)
                })
            });
        })
    }

    fn metrics(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("metrics", |endpoint| {
                endpoint.handle(move |mut client, _params| {
                    let encoder = prometheus::TextEncoder::new();
                    let metric_familys = prometheus::gather();
                    let mut buffer = vec![];
                    encoder.encode(&metric_familys, &mut buffer).unwrap();
                    client.set_content_type(encoder.format_type().parse::<Mime>().unwrap());
                    client.text(String::from_utf8(buffer).unwrap())
                })
            });
        })
    }

    fn reverse(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("reverse", |endpoint| {
                endpoint.params(|params| {
                    coord_param(params, false);
                    timeout_param(params);
                });
                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let coord = ::mimir::Coord::new(
                        params.find("lon").and_then(|p| p.as_f64()).unwrap(),
                        params.find("lat").and_then(|p| p.as_f64()).unwrap(),
                    );
                    let mut rubber = Rubber::new(&cnx);
                    rubber.set_read_timeout(parse_timeout(params));
                    let model_autocomplete =
                        rubber.get_address(&coord).map_err(model::BragiError::from);

                    let response = model::v1::AutocompleteResponse::from(model_autocomplete);
                    render(client, response)
                })
            });
        })
    }

    fn features(&self) -> rustless::Api {
        Api::build(|api| {
            api.get("features/:id", |endpoint| {
                endpoint.params(|params| {
                    params.opt_typed("id", json_dsl::string());
                    dataset_param(params);
                    timeout_param(params);
                });

                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let id = params.find("id").unwrap().as_str().unwrap();
                    let pt_datasets = get_param_array(params, "pt_dataset");
                    let all_data = params
                        .find("_all_data")
                        .map_or(false, |val| val.as_bool().unwrap());
                    let timeout = parse_timeout(params);
                    let features = query::features(&pt_datasets, all_data, &cnx, &id, timeout);
                    let response = model::v1::AutocompleteResponse::from(features);
                    render(client, response)
                })
            });
        })
    }

    fn autocomplete(&self) -> rustless::Api {
        Api::build(|api| {
            api.post("autocomplete", |endpoint| {
                endpoint.params(|params| {
                    params.opt_typed("q", json_dsl::string());
                    dataset_param(params);
                    paginate_param(params);
                    shape_param(params);
                    types_param(params);
                    timeout_param(params);
                });

                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let q = params
                        .find("q")
                        .and_then(|val| val.as_str())
                        .unwrap_or("")
                        .to_string();
                    let pt_datasets = get_param_array(params, "pt_dataset");
                    let all_data = params
                        .find("_all_data")
                        .map_or(false, |val| val.as_bool().unwrap());
                    let offset = params
                        .find("offset")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_OFFSET);
                    let limit = params
                        .find("limit")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_LIMIT);
                    let geometry = params.find_path(&["shape", "geometry"]).unwrap();
                    let coordinates = geometry
                        .find_path(&["coordinates"])
                        .unwrap()
                        .as_array()
                        .unwrap();
                    let mut shape = Vec::new();
                    for ar in coordinates[0].as_array().unwrap() {
                        // (Lat, Lon)
                        shape.push((
                            ar.as_array().unwrap()[1].as_f64().unwrap(),
                            ar.as_array().unwrap()[0].as_f64().unwrap(),
                        ));
                    }
                    let types = get_param_array(params, "type");
                    let timeout = parse_timeout(params);
                    let model_autocomplete = query::autocomplete(
                        &q,
                        &pt_datasets,
                        all_data,
                        offset,
                        limit,
                        None,
                        &cnx,
                        Some(shape),
                        &types,
                        timeout,
                    );
                    let response = model::v1::AutocompleteResponse::from(model_autocomplete);
                    render(client, response)
                })
            });
            api.get("autocomplete", |endpoint| {
                endpoint.params(|params| {
                    params.opt_typed("q", json_dsl::string());
                    dataset_param(params);
                    paginate_param(params);
                    coord_param(params, true);
                    types_param(params);
                    timeout_param(params);
                });
                let cnx = self.es_cnx_string.clone();
                endpoint.handle(move |client, params| {
                    let q = params
                        .find("q")
                        .and_then(|val| val.as_str())
                        .unwrap_or("")
                        .to_string();
                    let pt_datasets = get_param_array(params, "pt_dataset");
                    let all_data = params
                        .find("_all_data")
                        .map_or(false, |val| val.as_bool().unwrap());
                    let offset = params
                        .find("offset")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_OFFSET);
                    let limit = params
                        .find("limit")
                        .and_then(|val| val.as_u64())
                        .unwrap_or(DEFAULT_LIMIT);
                    let lon = params.find("lon").and_then(|p| p.as_f64());
                    let lat = params.find("lat").and_then(|p| p.as_f64());
                    // we have already checked that if there is a lon, lat
                    // is not None so we can unwrap
                    let coord = lon.and_then(|lon| {
                        Some(Coord {
                            lon: lon,
                            lat: lat.unwrap(),
                        })
                    });

                    let types = get_param_array(params, "type");
                    let timeout = parse_timeout(params);
                    let model_autocomplete = query::autocomplete(
                        &q,
                        &pt_datasets,
                        all_data,
                        offset,
                        limit,
                        coord,
                        &cnx,
                        None,
                        &types,
                        timeout,
                    );

                    let mut response = model::v1::AutocompleteResponse::from(model_autocomplete);

                    // Optional : add distance for each feature (in meters)
                    use model::v1::AutocompleteResponse::Autocomplete;
                    if let (Some(coord), Autocomplete(autocomplete_resp)) = (&coord, &mut response)
                    {
                        add_distance(autocomplete_resp, coord);
                    }

                    render(client, response)
                })
            });
        })
    }
}
<|endoftext|>


<commit_before>import platform::osmain;
import geom::*;
import comm::*;
import layout::display_list::*;
import azure::*;
import azure::bindgen::*;

enum msg {
    render(display_list),
    exit(comm::chan<()>)
}

fn renderer(osmain: chan<osmain::msg>) -> chan<msg> {
    task::spawn_listener::<msg> {|po|
        listen {|draw_target_ch|
            #debug("renderer: beginning rendering loop");
            osmain.send(osmain::begin_drawing(draw_target_ch));

            loop {
                alt po.recv() {
                  render(display_list) {
                    #debug("renderer: got render request");
                    let draw_target = draw_target_ch.recv();
                    #debug("renderer: rendering");
                    draw_display_list(draw_target, display_list);
                    #debug("renderer: returning surface");
                    osmain.send(osmain::draw(draw_target_ch, draw_target));
                  }
                  exit(response_ch) {
                    response_ch.send(());
                    break;
                  }
                }
            }
        }
    }
}

fn draw_display_list(
    draw_target: AzDrawTargetRef,
    display_list: display_list
) {
    clear(draw_target);

    for display_list.each {|item|
        let (r, g, b) = alt check item.item_type {
          solid_color(r, g, b) { (r, g, b) }
        };
        let bounds = (*item).bounds;

        let to_float = fn@(u: u8) -> float {
            (u as float) / 255f
        };

        let red_color = {
            r: to_float(r) as AzFloat,
            g: to_float(g) as AzFloat,
            b: to_float(b) as AzFloat,
            a: 1f as AzFloat
        };
        let red_pattern = AzCreateColorPattern(ptr::addr_of(red_color));

        let red_rect = {
            x: au_to_int(bounds.origin.x) as AzFloat,
            y: au_to_int(bounds.origin.y) as AzFloat,
            width: au_to_int(bounds.size.width) as AzFloat,
            height: au_to_int(bounds.size.height) as AzFloat
        };
        AzDrawTargetFillRect(
            draw_target,
            ptr::addr_of(red_rect),
            unsafe { unsafe::reinterpret_cast(red_pattern) }
        );

        AzReleaseColorPattern(red_pattern);
    }
}

fn clear(draw_target: AzDrawTargetRef) {

    let black_color = {
        r: 0f as AzFloat,
        g: 0f as AzFloat,
        b: 0f as AzFloat,
        a: 1f as AzFloat
    };
    let black_pattern = AzCreateColorPattern(ptr::addr_of(black_color));

    let black_rect = {
        x: 0 as AzFloat,
        y: 0 as AzFloat,
        width: 800 as AzFloat,
        height: 600 as AzFloat,
    };

    AzDrawTargetFillRect(
        draw_target,
        ptr::addr_of(black_rect),
        unsafe { unsafe::reinterpret_cast(black_pattern) }
    );

}<commit_msg>Free color patterns<commit_after>import platform::osmain;
import geom::*;
import comm::*;
import layout::display_list::*;
import azure::*;
import azure::bindgen::*;

enum msg {
    render(display_list),
    exit(comm::chan<()>)
}

fn renderer(osmain: chan<osmain::msg>) -> chan<msg> {
    task::spawn_listener::<msg> {|po|
        listen {|draw_target_ch|
            #debug("renderer: beginning rendering loop");
            osmain.send(osmain::begin_drawing(draw_target_ch));

            loop {
                alt po.recv() {
                  render(display_list) {
                    #debug("renderer: got render request");
                    let draw_target = draw_target_ch.recv();
                    #debug("renderer: rendering");
                    draw_display_list(draw_target, display_list);
                    #debug("renderer: returning surface");
                    osmain.send(osmain::draw(draw_target_ch, draw_target));
                  }
                  exit(response_ch) {
                    response_ch.send(());
                    break;
                  }
                }
            }
        }
    }
}

fn draw_display_list(
    draw_target: AzDrawTargetRef,
    display_list: display_list
) {
    clear(draw_target);

    for display_list.each {|item|
        let (r, g, b) = alt check item.item_type {
          solid_color(r, g, b) { (r, g, b) }
        };
        let bounds = (*item).bounds;

        let to_float = fn@(u: u8) -> float {
            (u as float) / 255f
        };

        let red_color = {
            r: to_float(r) as AzFloat,
            g: to_float(g) as AzFloat,
            b: to_float(b) as AzFloat,
            a: 1f as AzFloat
        };
        let red_pattern = AzCreateColorPattern(ptr::addr_of(red_color));

        let red_rect = {
            x: au_to_int(bounds.origin.x) as AzFloat,
            y: au_to_int(bounds.origin.y) as AzFloat,
            width: au_to_int(bounds.size.width) as AzFloat,
            height: au_to_int(bounds.size.height) as AzFloat
        };
        AzDrawTargetFillRect(
            draw_target,
            ptr::addr_of(red_rect),
            unsafe { unsafe::reinterpret_cast(red_pattern) }
        );

        AzReleaseColorPattern(red_pattern);
    }
}

fn clear(draw_target: AzDrawTargetRef) {

    let black_color = {
        r: 0f as AzFloat,
        g: 0f as AzFloat,
        b: 0f as AzFloat,
        a: 1f as AzFloat
    };
    let black_pattern = AzCreateColorPattern(ptr::addr_of(black_color));

    let black_rect = {
        x: 0 as AzFloat,
        y: 0 as AzFloat,
        width: 800 as AzFloat,
        height: 600 as AzFloat,
    };

    AzDrawTargetFillRect(
        draw_target,
        ptr::addr_of(black_rect),
        unsafe { unsafe::reinterpret_cast(black_pattern) }
    );

    AzReleaseColorPattern(black_pattern);
}<|endoftext|>