Skip to content

Commit 4f03585

Browse files
committed
feat: allow tuples to be indexed by compile time constants (for now)
1 parent 3e9e294 commit 4f03585

3 files changed

Lines changed: 228 additions & 12 deletions

File tree

src/compiler.ts

Lines changed: 125 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import {
5858
isConstExpressionNaN,
5959
ensureType,
6060
createType,
61+
expandType,
6162
getConstValueInteger
6263
} from "./module";
6364

@@ -473,6 +474,10 @@ export class Compiler extends DiagnosticEmitter {
473474
overrideStubs: Set<Function> = new Set();
474475
/** Elements currently undergoing compilation. */
475476
pendingElements: Set<Element> = new Set();
477+
/** Component locals for tuple-capturing locals (first component at index 0). */
478+
private tupleLocalComponents: Map<Local,Local[]> = new Map();
479+
/** TypeRef overrides for hidden locals that use tuple-typed Binaryen IR locals. */
480+
private localTypeRefOverrides: Map<Local,TypeRef> = new Map();
476481
/** Elements, that are module exports, already processed */
477482
doneModuleExports: Set<Element> = new Set();
478483
/** Shadow stack reference. */
@@ -739,7 +744,7 @@ export class Compiler extends DiagnosticEmitter {
739744
startFunctionInstance.internalName,
740745
signature.paramRefs,
741746
signature.resultRefs,
742-
typesToRefs(startFunctionInstance.getNonParameterLocalTypes()),
747+
typesToRefs(startFunctionInstance.getNonParameterLocalTypes(), this.getNonParameterLocalTypeRefOverrides(startFunctionInstance)),
743748
module.flatten(startFunctionBody)
744749
);
745750
startFunctionInstance.finalize(module, funcRef);
@@ -1681,7 +1686,7 @@ export class Compiler extends DiagnosticEmitter {
16811686
instance.internalName,
16821687
signature.paramRefs,
16831688
signature.resultRefs,
1684-
typesToRefs(instance.getNonParameterLocalTypes()),
1689+
typesToRefs(instance.getNonParameterLocalTypes(), this.getNonParameterLocalTypeRefOverrides(instance)),
16851690
module.flatten(stmts, signature.resultRefs)
16861691
);
16871692

@@ -1922,7 +1927,7 @@ export class Compiler extends DiagnosticEmitter {
19221927
getterInstance.internalName,
19231928
thisTypeRef,
19241929
valueTypeRef,
1925-
typesToRefs(getterInstance.getNonParameterLocalTypes()),
1930+
typesToRefs(getterInstance.getNonParameterLocalTypes(), this.getNonParameterLocalTypeRefOverrides(getterInstance)),
19261931
body
19271932
);
19281933
}
@@ -3174,7 +3179,7 @@ export class Compiler extends DiagnosticEmitter {
31743179
let pendingElements = this.pendingElements;
31753180
let temp = flow.addScopedDummyLocal(name, Type.auto, statement); // pending dummy
31763181
pendingElements.add(temp);
3177-
initExpr = this.compileExpression(initializerNode, Type.auto); // reports
3182+
initExpr = this.compileExpression(initializerNode, Type.auto, Constraints.AllowMultiValueResult);
31783183
initType = this.currentType;
31793184
pendingElements.delete(temp);
31803185
flow.freeScopedDummyLocal(name);
@@ -3309,9 +3314,43 @@ export class Compiler extends DiagnosticEmitter {
33093314
if (isConst) flow.setLocalFlag(local.index, LocalFlags.Constant);
33103315
}
33113316
if (initExpr) {
3312-
initializers.push(
3313-
this.makeLocalAssignment(local, initExpr, initType ? initType : type, false)
3314-
);
3317+
let tupleReturnTypes: Type[] | null = null;
3318+
let tupleCaptureNode = assert(initializerNode);
3319+
while (tupleCaptureNode.kind == NodeKind.Parenthesized)
3320+
tupleCaptureNode = (<ParenthesizedExpression>tupleCaptureNode).expression;
3321+
if (tupleCaptureNode.kind == NodeKind.Call && this.options.hasFeature(Feature.MultiValue))
3322+
tupleReturnTypes = this.resolveTupleReturnTypesFromExpressionRef(initExpr);
3323+
if (tupleReturnTypes && tupleReturnTypes.length > 1) {
3324+
let tupleLocals = new Array<Local>(tupleReturnTypes.length);
3325+
tupleLocals[0] = local;
3326+
for (let j = 1, k = tupleReturnTypes.length; j < k; ++j) {
3327+
let tupleLocal = flow.targetFunction.addLocal(tupleReturnTypes[j]);
3328+
flow.unsetLocalFlag(tupleLocal.index, ~0);
3329+
tupleLocals[j] = tupleLocal;
3330+
}
3331+
this.tupleLocalComponents.set(local, tupleLocals);
3332+
this.resolver.tupleLocalReturnTypes.set(local, tupleReturnTypes);
3333+
3334+
let tupleTypeRef = createType(typesToRefs(tupleReturnTypes));
3335+
let tupleStorage = flow.targetFunction.addLocal(tupleReturnTypes[0]);
3336+
flow.unsetLocalFlag(tupleStorage.index, ~0);
3337+
this.localTypeRefOverrides.set(tupleStorage, tupleTypeRef);
3338+
3339+
let tupleInit = new Array<ExpressionRef>(1 + tupleReturnTypes.length);
3340+
tupleInit[0] = module.local_set(tupleStorage.index, initExpr, false);
3341+
for (let j = 0, k = tupleReturnTypes.length; j < k; ++j) {
3342+
let tupleType = tupleReturnTypes[j];
3343+
tupleInit[1 + j] = this.makeLocalAssignment(
3344+
tupleLocals[j],
3345+
module.tuple_extract(module.local_get(tupleStorage.index, tupleTypeRef), j),
3346+
tupleType,
3347+
false
3348+
);
3349+
}
3350+
initializers.push(module.flatten(tupleInit));
3351+
} else {
3352+
initializers.push(this.makeLocalAssignment(local, initExpr, initType ? initType : type, false));
3353+
}
33153354
} else {
33163355
// no need to assign zero
33173356
if (local.type.isShortIntegerValue) {
@@ -3326,6 +3365,48 @@ export class Compiler extends DiagnosticEmitter {
33263365
: module.flatten(initializers);
33273366
}
33283367

3368+
/** Resolves tuple return types from a compiled expression, if it has a tuple Binaryen type. */
3369+
private resolveTupleReturnTypesFromExpressionRef(expression: ExpressionRef): Type[] | null {
3370+
let typeRefs = expandType(getExpressionType(expression));
3371+
let numTypes = typeRefs.length;
3372+
if (numTypes <= 1) return null;
3373+
let returnTypes = new Array<Type>(numTypes);
3374+
for (let i = 0; i < numTypes; ++i) {
3375+
switch (unchecked(typeRefs[i])) {
3376+
case TypeRef.I32: unchecked(returnTypes[i] = Type.i32); break;
3377+
case TypeRef.I64: unchecked(returnTypes[i] = Type.i64); break;
3378+
case TypeRef.F32: unchecked(returnTypes[i] = Type.f32); break;
3379+
case TypeRef.F64: unchecked(returnTypes[i] = Type.f64); break;
3380+
case TypeRef.V128: unchecked(returnTypes[i] = Type.v128); break;
3381+
default: return null;
3382+
}
3383+
}
3384+
return returnTypes;
3385+
}
3386+
3387+
/** Gets optional non-parameter local type ref overrides for a function, if any are present. */
3388+
private getNonParameterLocalTypeRefOverrides(instance: Function): TypeRef[] | null {
3389+
let localTypeRefOverrides = this.localTypeRefOverrides;
3390+
if (localTypeRefOverrides.size == 0) return null;
3391+
let localsByIndex = instance.localsByIndex;
3392+
let signature = instance.signature;
3393+
let numFixed = signature.parameterTypes.length;
3394+
if (signature.thisType) ++numFixed;
3395+
let numAdditional = localsByIndex.length - numFixed;
3396+
let refs = new Array<TypeRef>(numAdditional);
3397+
let hasOverride = false;
3398+
for (let i = 0; i < numAdditional; ++i) {
3399+
let local = localsByIndex[numFixed + i];
3400+
let ref = local.type.toRef();
3401+
if (localTypeRefOverrides.has(local)) {
3402+
ref = assert(localTypeRefOverrides.get(local));
3403+
hasOverride = true;
3404+
}
3405+
refs[i] = ref;
3406+
}
3407+
return hasOverride ? refs : null;
3408+
}
3409+
33293410
private compileVoidStatement(
33303411
statement: VoidStatement
33313412
): ExpressionRef {
@@ -5911,6 +5992,15 @@ export class Compiler extends DiagnosticEmitter {
59115992
switch (target.kind) {
59125993
case ElementKind.Local: {
59135994
let local = <Local>target;
5995+
if (this.tupleLocalComponents.has(local)) {
5996+
this.error(
5997+
DiagnosticCode.Not_implemented_0,
5998+
valueExpression.range,
5999+
"Assigning to locals that capture multi-value call results is not supported yet"
6000+
);
6001+
this.currentType = tee ? local.type : Type.void;
6002+
return module.unreachable();
6003+
}
59146004
if (flow.isLocalFlag(local.index, LocalFlags.Constant, true)) {
59156005
this.error(
59166006
DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property,
@@ -6759,7 +6849,10 @@ export class Compiler extends DiagnosticEmitter {
67596849
stub.internalName,
67606850
stub.signature.paramRefs,
67616851
stub.signature.resultRefs,
6762-
typesToRefs(stub.getNonParameterLocalTypes()),
6852+
typesToRefs(
6853+
stub.getNonParameterLocalTypes(),
6854+
this.getNonParameterLocalTypeRefOverrides(stub)
6855+
),
67636856
module.flatten(stmts, stub.signature.resultRefs)
67646857
);
67656858
stub.set(CommonFlags.Compiled);
@@ -7267,7 +7360,30 @@ export class Compiler extends DiagnosticEmitter {
72677360
let module = this.module;
72687361
let targetExpression = expression.expression;
72697362
let resolver = this.resolver;
7270-
let targetElement = resolver.lookupExpression(targetExpression, this.currentFlow, Type.auto, ReportMode.Swallow);
7363+
let target = resolver.lookupExpression(targetExpression, this.currentFlow, Type.auto, ReportMode.Swallow);
7364+
if (target && target.kind == ElementKind.Local) {
7365+
let tupleLocals = this.tupleLocalComponents.get(<Local>target);
7366+
if (tupleLocals) {
7367+
let index = resolver.resolveTupleElementLiteralIndex(
7368+
expression.elementExpression,
7369+
tupleLocals.length,
7370+
ReportMode.Report
7371+
);
7372+
if (index < 0) return module.unreachable();
7373+
let tupleLocal = tupleLocals[index];
7374+
if (!this.currentFlow.isLocalFlag(tupleLocal.index, LocalFlags.Initialized)) {
7375+
this.error(
7376+
DiagnosticCode.Variable_0_is_used_before_being_assigned,
7377+
expression.range, tupleLocal.name
7378+
);
7379+
}
7380+
let tupleElementType = tupleLocal.type;
7381+
this.currentType = tupleElementType;
7382+
return module.local_get(tupleLocal.index, tupleElementType.toRef());
7383+
}
7384+
}
7385+
7386+
let targetElement = target;
72717387
if (targetElement && targetElement.kind == ElementKind.Enum) {
72727388
const elementExpr = this.compileExpression(expression.elementExpression, Type.i32, Constraints.ConvImplicit);
72737389
const toStringFunctionName = this.ensureEnumToString(<Enum>targetElement, expression);

src/resolver.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
ClassPrototype,
2727
Interface,
2828
Function,
29+
Local,
2930
FunctionPrototype,
3031
VariableLikeElement,
3132
Property,
@@ -130,6 +131,8 @@ export class Resolver extends DiagnosticEmitter {
130131
currentElementExpression : Expression | null = null;
131132
/** Whether a new override has been discovered. */
132133
discoveredOverride: bool = false;
134+
/** Tuple return types captured in locals initialized from multi-value calls. */
135+
tupleLocalReturnTypes: Map<Local,Type[]> = new Map();
133136

134137
/** Constructs the resolver for the specified program. */
135138
constructor(
@@ -140,6 +143,26 @@ export class Resolver extends DiagnosticEmitter {
140143
this.program = program;
141144
}
142145

146+
/** Gets a tuple-capturing local from an expression, if any. */
147+
private lookupTupleCapturingLocalExpression(
148+
expression: Expression,
149+
ctxFlow: Flow
150+
): Local | null {
151+
while (expression.kind == NodeKind.Parenthesized) {
152+
expression = (<ParenthesizedExpression>expression).expression;
153+
}
154+
if (expression.kind != NodeKind.Identifier) return null;
155+
let localElement = this.lookupIdentifierExpression(
156+
<IdentifierExpression>expression,
157+
ctxFlow,
158+
ctxFlow.sourceFunction,
159+
ReportMode.Swallow
160+
);
161+
if (!localElement || localElement.kind != ElementKind.Local) return null;
162+
let local = <Local>localElement;
163+
return this.tupleLocalReturnTypes.has(local) ? local : null;
164+
}
165+
143166
// ====================================================== Types ======================================================
144167

145168
/** Resolves a {@link TypeNode} to a concrete {@link Type}. */
@@ -1299,6 +1322,15 @@ export class Resolver extends DiagnosticEmitter {
12991322
}
13001323
let element = this.lookupIdentifierExpression(node, ctxFlow, ctxElement, reportMode);
13011324
if (!element) return null;
1325+
if (element.kind == ElementKind.Local && this.tupleLocalReturnTypes.has(<Local>element)) {
1326+
if (reportMode == ReportMode.Report) {
1327+
this.error(
1328+
DiagnosticCode.Not_implemented_0,
1329+
node.range, "Using multi-value local results outside of tuple indexing"
1330+
);
1331+
}
1332+
return null;
1333+
}
13021334
if (element.kind == ElementKind.FunctionPrototype) {
13031335
let instance = this.resolveFunction(<FunctionPrototype>element, null, new Map(), reportMode);
13041336
if (!instance) return null;
@@ -1594,6 +1626,64 @@ export class Resolver extends DiagnosticEmitter {
15941626
return null;
15951627
}
15961628

1629+
/** Resolves tuple-local indexing if this is an indexed access on a tuple-capturing local. */
1630+
resolveTupleLocalElementAccessExpression(
1631+
node: ElementAccessExpression,
1632+
ctxFlow: Flow,
1633+
reportMode: ReportMode = ReportMode.Report
1634+
): Type | null {
1635+
let targetExpression = node.expression;
1636+
let tupleLocal = this.lookupTupleCapturingLocalExpression(targetExpression, ctxFlow);
1637+
if (!tupleLocal) return null;
1638+
let tupleReturnTypes = assert(this.tupleLocalReturnTypes.get(tupleLocal));
1639+
let index = this.resolveTupleElementLiteralIndex(node.elementExpression, tupleReturnTypes.length, reportMode);
1640+
if (index < 0) return null;
1641+
this.currentThisExpression = targetExpression;
1642+
this.currentElementExpression = node.elementExpression;
1643+
return tupleReturnTypes[index];
1644+
}
1645+
1646+
/** Resolves a tuple element index, requiring a non-negative integer literal in bounds. */
1647+
resolveTupleElementLiteralIndex(
1648+
expression: Expression,
1649+
tupleLength: i32,
1650+
reportMode: ReportMode = ReportMode.Report
1651+
): i32 {
1652+
if (
1653+
expression.kind != NodeKind.Literal ||
1654+
!(<LiteralExpression>expression).isLiteralKind(LiteralKind.Integer)
1655+
) {
1656+
if (reportMode == ReportMode.Report) {
1657+
this.error(
1658+
DiagnosticCode.Not_implemented_0,
1659+
expression.range, "Tuple indexing currently requires a constant integer literal index"
1660+
);
1661+
}
1662+
return -1;
1663+
}
1664+
let value = (<IntegerLiteralExpression>expression).value;
1665+
if (i64_high(value) != 0) {
1666+
if (reportMode == ReportMode.Report) {
1667+
this.error(
1668+
DiagnosticCode.Not_implemented_0,
1669+
expression.range, "Tuple index is out of bounds"
1670+
);
1671+
}
1672+
return -1;
1673+
}
1674+
let index = i64_low(value);
1675+
if (index < 0 || index >= tupleLength) {
1676+
if (reportMode == ReportMode.Report) {
1677+
this.error(
1678+
DiagnosticCode.Not_implemented_0,
1679+
expression.range, "Tuple index is out of bounds"
1680+
);
1681+
}
1682+
return -1;
1683+
}
1684+
return index;
1685+
}
1686+
15971687
/** Resolves an element access expression to its static type. */
15981688
private resolveElementAccessExpression(
15991689
/** The expression to resolve. */
@@ -1605,6 +1695,9 @@ export class Resolver extends DiagnosticEmitter {
16051695
/** How to proceed with eventual diagnostics. */
16061696
reportMode: ReportMode = ReportMode.Report
16071697
): Type | null {
1698+
let tupleElementType = this.resolveTupleLocalElementAccessExpression(node, ctxFlow, reportMode);
1699+
if (tupleElementType) return tupleElementType;
1700+
if (this.lookupTupleCapturingLocalExpression(node.expression, ctxFlow)) return null;
16081701
let element = this.lookupElementAccessExpression(node, ctxFlow, ctxType, reportMode);
16091702
if (!element) return null;
16101703
let type = this.getTypeOfElement(element);

src/types.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -910,11 +910,18 @@ export class Type {
910910
}
911911

912912
/** Converts an array of types to an array of type references. */
913-
export function typesToRefs(types: Type[]): TypeRef[] {
913+
export function typesToRefs(types: Type[], refsOverride: TypeRef[] | null = null): TypeRef[] {
914914
let numTypes = types.length;
915915
let ret = new Array<TypeRef>(numTypes);
916-
for (let i = 0; i < numTypes; ++i) {
917-
unchecked(ret[i] = types[i].toRef());
916+
if (refsOverride) {
917+
assert(refsOverride.length == numTypes);
918+
for (let i = 0; i < numTypes; ++i) {
919+
unchecked(ret[i] = refsOverride[i]);
920+
}
921+
} else {
922+
for (let i = 0; i < numTypes; ++i) {
923+
unchecked(ret[i] = types[i].toRef());
924+
}
918925
}
919926
return ret;
920927
}

0 commit comments

Comments
 (0)