Skip to content

Commit

Permalink
asm-llvm: parse/validate function table dereferences.
Browse files Browse the repository at this point in the history
These are a bit tricky because they are all forward references, but we've
already done the hard part.  We just return a new TableBinding type which
lets us defer typechecking (and codegen) until we see the call arguments.
We can then infer a type for the function table and check it for consistency.
  • Loading branch information
cscott committed Jun 6, 2013
1 parent 155cd0b commit 2e5f4c8
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 31 deletions.
97 changes: 67 additions & 30 deletions asm-llvm.js
Original file line number Diff line number Diff line change
Expand Up @@ -1524,9 +1524,9 @@ define([], function asm_llvm() {

// Function table references propagate some type information
// so that we can infer the type before seeing the declaration.
var TableBinding = function(base, size, temp) {
var TableBinding = function(base, index, temp) {
this.base = base;
this.size = size;
this.index = index;
this.type = base.type;
this.temp = temp || gensym();
};
Expand Down Expand Up @@ -1868,7 +1868,7 @@ define([], function asm_llvm() {
} else if (property.andy) {
// Try to parse as a function table lookup:
// `x:Identifier[index:Expression & n:NumericLiteral](arg:Expression,...)`
raise(startPos, "unimplemented");
return TableBinding.New(base, property);
} else {
raise(startPos, "bad member expression");
}
Expand All @@ -1888,37 +1888,62 @@ define([], function asm_llvm() {
var startPos = lastStart;
var callArguments = parseExprList(_parenR, false);
// Type check the call based on the argument types.
var binding;
if (base.type===Types.Function) {
callArguments.forEach(function(b, i) {
var binding, ty;
var argTypes = callArguments.map(function(b){
return defcheck(b).type;
});
// If this is a forward reference to a future function,
// infer the function type based on 'plusCoerced' (the
// surrounding context) and the argument types.
var makeFunctionTypeFromContext = function() {
return Types.Arrow(argTypes.map(function(ty) {
// All argument types must be either 'double' or 'int'.
if (ty.isSubtypeOf(Types.Double)) {
return Types.Double;
} else if (ty.isSubtypeOf(Types.Int)) {
return Types.Int;
} else {
raise(startPos, "function arguments must be "+
"coerced to either double or int");
}
// Return type is either 'doublish' or 'intish'
}), plusCoerced ? Types.Doublish : Types.Intish);
};
if (TableBinding.hasInstance(base)) {
// Handle an indirect invocation through a function table.
if (!powerOf2(base.index.andAmount+1)) {
raise(startPos,
"function table size must be a power of 2");
}
if (base.base.type === Types.ForwardReference) {
base.base.type =
Types.Table(makeFunctionTypeFromContext(),
base.index.andAmount+1);
}
if (!base.base.type.table) {
raise(startPos, "base should be function table");
}
if (base.base.type.size !== (base.index.andAmount+1)) {
raise(startPos, "function table size mismatch");
}
ty = base.base.type.base.apply(argTypes);
if (ty===null) {
raise(startPos, "call argument mismatch; wants "+
base.type.base.toString());
}
binding = TempBinding.New(ty);
} else if (base.type===Types.Function) {
// Handle foreign function invocation.
argTypes.forEach(function(type, i) {
var msg = "argument "+i+" to foreign function";
defcheck(b, msg);
typecheck(b.type, Types.Extern, msg, startPos);
typecheck(type, Types.Extern, msg, startPos);
});
binding = TempBinding.New(Types.Unknown);
} else if (base.type.arrow || base.type.functiontypes ||
base.type === Types.ForwardReference) {
var argTypes = callArguments.map(function(b){
return defcheck(b).type;
});
var ty;
// If this is a forward reference to a future function,
// infer the function type based on 'plusCoerced' (the
// surrounding context) and the argument types.
// Handle direct invocation of local function.
if (base.type === Types.ForwardReference) {
// All argument types must be either 'double' or 'int'.
var nt = Types.Arrow(argTypes.map(function(ty) {
if (ty.isSubtypeOf(Types.Double)) {
return Types.Double;
} else if (ty.isSubtypeOf(Types.Int)) {
return Types.Int;
} else {
raise(startPos, "function arguments must be "+
"coerced to either double or int");
}
// Return type is either 'doublish' or 'intish'
}), plusCoerced ? Types.Doublish : Types.Intish);
base.type = mergeTypes(base.type, nt, startPos);
base.type = makeFunctionTypeFromContext();
}
ty = base.type.apply(argTypes);
if (ty===null) {
Expand All @@ -1934,7 +1959,11 @@ define([], function asm_llvm() {
};

var parseExprSubscripts = function(plusCoerced) {
return parseSubscripts(parseExprAtom(), plusCoerced);
var b = parseSubscripts(parseExprAtom(), plusCoerced);
if (TableBinding.hasInstance(b)) {
raise(tokStart, "incomplete function table lookup");
}
return b;
};

// Parse unary operators, both prefix and postfix.
Expand Down Expand Up @@ -2578,7 +2607,15 @@ define([], function asm_llvm() {
raise(yPos, "Function table length is not a power of 2.");
}
ty = Types.Table(lastType, table.length);
module.env.bind(x, GlobalBinding.New(ty, false), startPos);
var binding = module.env.lookup(x);
if (binding === null || !binding.pending) {
module.env.bind(x, GlobalBinding.New(ty, false), startPos);
} else {
console.assert(GlobalBinding.hasInstance(binding) &&
!binding.mutable);
binding.type = mergeTypes(binding.type, ty, startPos);
binding.pending = false; // binding is now authoritative.
}
module.seenTable = true;
} else if (module.seenTable) {
raise(tokStart, "expected function table");
Expand Down
2 changes: 1 addition & 1 deletion asm-tests/example-asm.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function mymodule(stdlib, foreign, heap) {
i = i|0;
x = x|0;
H32[i>>2] = x; // shifted by log2(byte count)
//ftable_2[(x-2)&1](); // dynamic call of functions in table 2
ftable_2[(x-2)&1](); // dynamic call of functions in table 2

// no return necessary when return type is void
}
Expand Down

0 comments on commit 2e5f4c8

Please sign in to comment.