Browse Source

Parse controlling type variable. Do basic type inference.

Replace the make_multi_inst() function with a make_inst_results() which uses
the constraint system to create the result values. A typevar argument ensures
that this function does not infer anything from the instruction data arguments.
These arguments may not be valid during parsing.

Implement basic type inference in the parser. If the designated value operand
on a polymorphic instruction refers to a known value, use that to infer the
controlling type variable.

This simple method of type inference requires the operand value to be defined
above the use in the text. Since reordering the EBBs could place a dominating
EBB below the current one, this is a bit fragile. One possibility would be to
require the value is defined in the same EBB. In all other cases, the
controlling typevar should be explicit.
pull/3/head
Jakob Stoklund Olesen 9 years ago
parent
commit
ecd8287eb0
  1. 40
      meta/gen_instr.py
  2. 13
      src/libcretonne/instructions.rs
  3. 70
      src/libcretonne/repr.rs
  4. 7
      src/libcretonne/types.rs
  5. 4
      src/libcretonne/write.rs
  6. 125
      src/libreader/parser.rs

40
meta/gen_instr.py

@ -70,6 +70,14 @@ def gen_instruction_data_impl(fmt):
'InstructionData::{} {{ ty, .. }} => ty,' 'InstructionData::{} {{ ty, .. }} => ty,'
.format(f.name)) .format(f.name))
fmt.doc_comment('Mutable reference to the type of the first result.')
with fmt.indented('pub fn first_type_mut(&mut self) -> &mut Type {', '}'):
with fmt.indented('match *self {', '}'):
for f in cretonne.InstructionFormat.all_formats:
fmt.line(
'InstructionData::{} {{ ref mut ty, .. }} => ty,'
.format(f.name))
# Generate shared and mutable accessors for `second_result` which only # Generate shared and mutable accessors for `second_result` which only
# applies to instruction formats that can produce multiple results. # applies to instruction formats that can produce multiple results.
# Everything else returns `None`. # Everything else returns `None`.
@ -120,6 +128,38 @@ def gen_instruction_data_impl(fmt):
' { ref mut second_result, .. }' + ' { ref mut second_result, .. }' +
' => Some(second_result),') ' => Some(second_result),')
fmt.doc_comment('Get the controlling type variable operand.')
with fmt.indented(
'pub fn typevar_operand(&self) -> Option<Value> {', '}'):
with fmt.indented('match *self {', '}'):
for f in cretonne.InstructionFormat.all_formats:
n = 'InstructionData::' + f.name
if f.typevar_operand is None:
fmt.line(n + ' { .. } => None,')
elif len(f.value_operands) == 1:
# We have a single value operand called 'arg'.
if f.boxed_storage:
fmt.line(
n + ' { ref data, .. } => Some(data.arg),')
else:
fmt.line(n + ' { arg, .. } => Some(arg),')
else:
# We have multiple value operands and an array `args`.
# Which `args` index to use?
# Map from index into f.kinds into f.value_operands
# index.
i = f.value_operands.index(f.typevar_operand)
if f.boxed_storage:
fmt.line(
n +
' {{ ref data, .. }} => Some(data.args[{}]),'
.format(i))
else:
fmt.line(
n +
' {{ ref args, .. }} => Some(args[{}]),'
.format(i))
def collect_instr_groups(targets): def collect_instr_groups(targets):
seen = set() seen = set()

13
src/libcretonne/instructions.rs

@ -142,15 +142,15 @@ pub enum InstructionData {
BinaryImm { BinaryImm {
opcode: Opcode, opcode: Opcode,
ty: Type, ty: Type,
lhs: Value, arg: Value,
rhs: Imm64, imm: Imm64,
}, },
// Same as BinaryImm, but the immediate is the lhs operand. // Same as BinaryImm, but the immediate is the lhs operand.
BinaryImmRev { BinaryImmRev {
opcode: Opcode, opcode: Opcode,
ty: Type, ty: Type,
rhs: Value, arg: Value,
lhs: Imm64, imm: Imm64,
}, },
BinaryOverflow { BinaryOverflow {
opcode: Opcode, opcode: Opcode,
@ -366,6 +366,11 @@ impl OpcodeConstraints {
pub fn ctrl_typeset(self) -> Option<ValueTypeSet> { pub fn ctrl_typeset(self) -> Option<ValueTypeSet> {
self.typeset_offset().map(|offset| TYPE_SETS[offset]) self.typeset_offset().map(|offset| TYPE_SETS[offset])
} }
/// Is this instruction polymorphic?
pub fn is_polymorphic(self) -> bool {
self.ctrl_typeset().is_some()
}
} }
/// A value type set describes the permitted set of types for a type variable. /// A value type set describes the permitted set of types for a type variable.

70
src/libcretonne/repr.rs

@ -88,8 +88,8 @@ impl Function {
/// Create a new instruction. /// Create a new instruction.
/// ///
/// The instruction is allowed to produce at most one result as indicated by `data.ty`. Use /// The type of the first result is indicated by `data.ty`. If the instruction produces
/// `make_multi_inst()` to create instructions with multiple results. /// multiple results, also call `make_inst_results` to allocate value table entries.
pub fn make_inst(&mut self, data: InstructionData) -> Inst { pub fn make_inst(&mut self, data: InstructionData) -> Inst {
let inst = Inst::new(self.instructions.len()); let inst = Inst::new(self.instructions.len());
self.instructions.push(data); self.instructions.push(data);
@ -101,40 +101,59 @@ impl Function {
inst inst
} }
/// Make an instruction that may produce multiple results. fn inst_mut(&mut self, inst: Inst) -> &mut InstructionData {
&mut self.instructions[inst.index()]
}
/// Create result values for an instruction that produces multiple results.
///
/// Instructions that produce 0 or 1 result values only need to be created with `make_inst`. If
/// the instruction may produce more than 1 result, call `make_inst_results` to allocate
/// `Value` table entries for the additional results.
///
/// The result value types are determined from the instruction's value type constraints and the
/// provided `ctrl_typevar` type for polymorphic instructions. For non-polymorphic
/// instructions, `ctrl_typevar` is ignored, and `VOID` can be used.
/// ///
/// The type of the first result is `data.ty`. If the instruction generates more than one /// The type of the first result value is also set, even if it was already set in the
/// result, additional result types are in `extra_result_types`. /// `InstructionData` passed to `make_inst`. If this function is called with a single-result
/// instruction, that is the only effect.
/// ///
/// Not all instruction formats can represent multiple result values. This function will panic /// Returns the number of results produced by the instruction.
/// if the format of `data` is insufficient. pub fn make_inst_results(&mut self, inst: Inst, ctrl_typevar: Type) -> usize {
pub fn make_multi_inst(&mut self, data: InstructionData, extra_result_types: &[Type]) -> Inst { let constraints = self[inst].opcode().constraints();
let inst = self.make_inst(data); let fixed_results = constraints.fixed_results();
if !extra_result_types.is_empty() {
// Additional values form a linked list starting from the second result value. Generate // Additional values form a linked list starting from the second result value. Generate
// the list backwards so we don't have to modify value table entries in place. (This // the list backwards so we don't have to modify value table entries in place. (This
// causes additional result values to be numbered backwards which is not the aestetic // causes additional result values to be numbered backwards which is not the aestetic
// choice, but since it is only visible in extremely rare instructions with 3+ results, // choice, but since it is only visible in extremely rare instructions with 3+ results,
// we don't care). // we don't care).
let mut head = NO_VALUE; let mut head = NO_VALUE;
for ty in extra_result_types.into_iter().rev() { let mut first_type = Type::default();
// TBD: Function call return values for direct and indirect function calls.
if fixed_results > 0 {
for res_idx in (1..fixed_results).rev() {
head = self.make_value(ValueData::Def { head = self.make_value(ValueData::Def {
ty: *ty, ty: constraints.result_type(res_idx, ctrl_typevar),
def: inst, def: inst,
next: head, next: head,
}); });
} }
first_type = constraints.result_type(0, ctrl_typevar);
}
// Update the second_result pointer in `inst`. // Update the second_result pointer in `inst`.
if let Some(second_result_ref) = self.instructions[inst.index()].second_result_mut() { if head != NO_VALUE {
*second_result_ref = head; *self.inst_mut(inst)
} else { .second_result_mut()
panic!("Instruction format doesn't allow multiple results."); .expect("instruction format doesn't allow multiple results") = head;
}
} }
*self.inst_mut(inst).first_type_mut() = first_type;
inst fixed_results
} }
/// Get the first result of an instruction. /// Get the first result of an instruction.
@ -521,21 +540,6 @@ mod tests {
assert_eq!(ins.first_type(), types::I32); assert_eq!(ins.first_type(), types::I32);
} }
#[test]
fn multiple_results() {
use types::*;
let mut func = Function::new();
let idata = InstructionData::call(Opcode::Vconst, I64);
let inst = func.make_multi_inst(idata, &[I8, F64]);
assert_eq!(inst.to_string(), "inst0");
let results: Vec<Value> = func.inst_results(inst).collect();
assert_eq!(results.len(), 3);
assert_eq!(func.value_type(results[0]), I64);
assert_eq!(func.value_type(results[1]), I8);
assert_eq!(func.value_type(results[2]), F64);
}
#[test] #[test]
fn stack_slot() { fn stack_slot() {
let mut func = Function::new(); let mut func = Function::new();

7
src/libcretonne/types.rs

@ -1,6 +1,7 @@
//! Common types for the Cretonne code generator. //! Common types for the Cretonne code generator.
use std::default::Default;
use std::fmt::{self, Display, Formatter, Write}; use std::fmt::{self, Display, Formatter, Write};
// ====--------------------------------------------------------------------------------------====// // ====--------------------------------------------------------------------------------------====//
@ -202,6 +203,12 @@ impl Display for Type {
} }
} }
impl Default for Type {
fn default() -> Type {
VOID
}
}
// ====--------------------------------------------------------------------------------------====// // ====--------------------------------------------------------------------------------------====//
// //
// Function signatures // Function signatures

4
src/libcretonne/write.rs

@ -153,8 +153,8 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result {
UnaryIeee64 { opcode, imm, .. } => writeln!(w, "{} {}", opcode, imm), UnaryIeee64 { opcode, imm, .. } => writeln!(w, "{} {}", opcode, imm),
UnaryImmVector { opcode, .. } => writeln!(w, "{} [...]", opcode), UnaryImmVector { opcode, .. } => writeln!(w, "{} [...]", opcode),
Binary { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]), Binary { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]),
BinaryImm { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), BinaryImm { opcode, arg, imm, .. } => writeln!(w, "{} {}, {}", opcode, arg, imm),
BinaryImmRev { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), BinaryImmRev { opcode, imm, arg, .. } => writeln!(w, "{} {}, {}", opcode, imm, arg),
BinaryOverflow { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]), BinaryOverflow { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]),
Select { opcode, args, .. } => { Select { opcode, args, .. } => {
writeln!(w, "{} {}, {}, {}", opcode, args[0], args[1], args[2]) writeln!(w, "{} {}, {}, {}", opcode, args[0], args[1], args[2])

125
src/libreader/parser.rs

@ -144,16 +144,20 @@ impl<'a> Parser<'a> {
} }
// Generate an error. // Generate an error.
fn error(&self, message: &str) -> Error { fn error_string(&self, message: String) -> Error {
Error { Error {
location: self.location, location: self.location,
message: message:
// If we have a lexer error latched, report that. // If we have a lexer error latched, report that.
match self.lex_error { match self.lex_error {
Some(lexer::Error::InvalidChar) => "invalid character".to_string(), Some(lexer::Error::InvalidChar) => "invalid character".to_string(),
None => message.to_string(), None => message,
}
} }
} }
fn error(&self, message: &str) -> Error {
self.error_string(message.to_string())
} }
// Match and consume a token without payload. // Match and consume a token without payload.
@ -511,14 +515,15 @@ impl<'a> Parser<'a> {
// Parse an instruction, append it to `ebb`. // Parse an instruction, append it to `ebb`.
// //
// instruction ::= [inst-results "="] Opcode(opc) ... // instruction ::= [inst-results "="] Opcode(opc) ["." Type] ...
// inst-results ::= Value(v) { "," Value(vx) } // inst-results ::= Value(v) { "," Value(vx) }
// //
fn parse_instruction(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { fn parse_instruction(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> {
// Result value numbers. // Result value numbers.
let mut results = Vec::new(); let mut results = Vec::new();
// instruction ::= * [inst-results "="] Opcode(opc) ... // instruction ::= * [inst-results "="] Opcode(opc) ["." Type] ...
// inst-results ::= * Value(v) { "," Value(vx) }
if let Some(Token::Value(v)) = self.token() { if let Some(Token::Value(v)) = self.token() {
self.consume(); self.consume();
results.push(v); results.push(v);
@ -532,7 +537,7 @@ impl<'a> Parser<'a> {
try!(self.match_token(Token::Equal, "expected '=' before opcode")); try!(self.match_token(Token::Equal, "expected '=' before opcode"));
} }
// instruction ::= [inst-results "="] * Opcode(opc) ... // instruction ::= [inst-results "="] * Opcode(opc) ["." Type] ...
let opcode = if let Some(Token::Identifier(text)) = self.token() { let opcode = if let Some(Token::Identifier(text)) = self.token() {
match text.parse() { match text.parse() {
Ok(opc) => opc, Ok(opc) => opc,
@ -541,24 +546,110 @@ impl<'a> Parser<'a> {
} else { } else {
return Err(self.error("expected instruction opcode")); return Err(self.error("expected instruction opcode"));
}; };
self.consume();
// Look for a controlling type variable annotation.
// instruction ::= [inst-results "="] Opcode(opc) * ["." Type] ...
let explicit_ctrl_type = if self.optional(Token::Dot) {
Some(try!(self.match_type("expected type after 'opcode.'")))
} else {
None
};
// instruction ::= [inst-results "="] Opcode(opc) * ... // instruction ::= [inst-results "="] Opcode(opc) ["." Type] * ...
let inst_data = try!(self.parse_inst_operands(opcode)); let inst_data = try!(self.parse_inst_operands(opcode));
// We're done parsing the instruction now.
//
// We still need to check that the number of result values in the source matches the opcode
// or function call signature. We also need to create values with the right type for all
// the instruction results.
let ctrl_typevar = try!(self.infer_typevar(ctx, opcode, explicit_ctrl_type, &inst_data));
let inst = ctx.function.make_inst(inst_data); let inst = ctx.function.make_inst(inst_data);
let num_results = ctx.function.make_inst_results(inst, ctrl_typevar);
ctx.function.append_inst(ebb, inst);
// TODO: Check that results.len() matches the opcode. if results.len() != num_results {
// TODO: Multiple results. let m = format!("instruction produces {} result values, {} given",
if !results.is_empty() { num_results,
assert!(results.len() == 1, "Multiple results not implemented"); results.len());
let result = ctx.function.first_result(inst); return Err(self.error_string(m));
try!(ctx.add_value(results[0], result, &self.location));
} }
ctx.function.append_inst(ebb, inst); // Now map the source result values to the just created instruction results.
// We need to copy the list of result values to avoid fighting the borrow checker.
let new_results: Vec<Value> = ctx.function.inst_results(inst).collect();
for (src, val) in results.iter().zip(new_results) {
try!(ctx.add_value(*src, val, &self.location));
}
Ok(()) Ok(())
} }
// Type inference for polymorphic instructions.
//
// The controlling type variable can be specified explicitly as 'splat.i32x4 v5', or it can be
// inferred from `inst_data.typevar_operand` for some opcodes.
//
// The value operands in `inst_data` are expected to use source numbering.
//
// Returns the controlling typevar for a polymorphic opcode, or `VOID` for a non-polymorphic
// opcode.
fn infer_typevar(&self,
ctx: &Context,
opcode: Opcode,
explicit_ctrl_type: Option<Type>,
inst_data: &InstructionData)
-> Result<Type> {
let constraints = opcode.constraints();
let ctrl_type = match explicit_ctrl_type {
Some(t) => t,
None => {
if constraints.use_typevar_operand() {
// This is an opcode that supports type inference, AND there was no explicit
// type specified. Look up `ctrl_value` to see if it was defined already.
// TBD: If it is defined in another block, the type should have been specified
// explicitly. It is unfortunate that the correctness of IL depends on the
// layout of the blocks.
let ctrl_src_value = inst_data.typevar_operand()
.expect("Constraints <-> Format inconsistency");
ctx.function.value_type(match ctx.values.get(&ctrl_src_value) {
Some(&v) => v,
None => {
let m = format!("cannot determine type of operand {}", ctrl_src_value);
return Err(self.error_string(m));
}
})
} else if constraints.is_polymorphic() {
// This opcode does not support type inference, so the explicit type variable
// is required.
return Err(self.error("type variable required for polymorphic opcode"));
} else {
// This is a non-polymorphic opcode. No typevar needed.
VOID
}
}
};
// Verify that `ctrl_type` is valid for the controlling type variable. We don't want to
// attempt deriving types from an incorrect basis.
// This is not a complete type check. The verifier does that.
if let Some(typeset) = constraints.ctrl_typeset() {
// This is a polymorphic opcode.
if !typeset.contains(ctrl_type) {
let m = format!("{} is not a valid typevar for {}", ctrl_type, opcode);
return Err(self.error_string(m));
}
} else {
// Treat it as a syntax error to speficy a typevar on a non-polymorphic opcode.
if ctrl_type != VOID {
return Err(self.error_string(format!("{} does not take a typevar", opcode)));
}
}
Ok(ctrl_type)
}
// Parse the operands following the instruction opcode. // Parse the operands following the instruction opcode.
// This depends on the format of the opcode. // This depends on the format of the opcode.
fn parse_inst_operands(&mut self, opcode: Opcode) -> Result<InstructionData> { fn parse_inst_operands(&mut self, opcode: Opcode) -> Result<InstructionData> {
@ -617,8 +708,8 @@ impl<'a> Parser<'a> {
InstructionData::BinaryImm { InstructionData::BinaryImm {
opcode: opcode, opcode: opcode,
ty: VOID, ty: VOID,
lhs: lhs, arg: lhs,
rhs: rhs, imm: rhs,
} }
} }
InstructionFormat::BinaryImmRev => { InstructionFormat::BinaryImmRev => {
@ -628,8 +719,8 @@ impl<'a> Parser<'a> {
InstructionData::BinaryImmRev { InstructionData::BinaryImmRev {
opcode: opcode, opcode: opcode,
ty: VOID, ty: VOID,
lhs: lhs, imm: lhs,
rhs: rhs, arg: rhs,
} }
} }
InstructionFormat::BinaryOverflow => { InstructionFormat::BinaryOverflow => {

Loading…
Cancel
Save