Skip to content

Commit

Permalink
Merge branch 'main' into SuspenseCallbackRN
Browse files Browse the repository at this point in the history
  • Loading branch information
bgirard authored Aug 26, 2024
2 parents 42ecf77 + dcae56f commit 130ae88
Show file tree
Hide file tree
Showing 25 changed files with 847 additions and 143 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/runtime_commit_artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ jobs:
build/oss-experimental/react-refresh/cjs/react-refresh-babel.development.js
- name: Insert @headers into eslint plugin and react-refresh
run: |
sed -i -e 's/ LICENSE file in the root directory of this source tree./ LICENSE file in the root directory of this source tree.\n * \n * @noformat\n * @nolint\n * @lightSyntaxTransform\n * @preventMunge\n * @oncall react_core/' \
sed -i -e 's/ LICENSE file in the root directory of this source tree./ LICENSE file in the root directory of this source tree.\n *\n * @noformat\n * @nolint\n * @lightSyntaxTransform\n * @preventMunge\n * @oncall react_core/' \
build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
build/oss-experimental/react-refresh/cjs/react-refresh-babel.development.js
- name: Move relevant files for React in www into compiled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,6 @@ export type Phi = {
kind: 'Phi';
id: Identifier;
operands: Map<BlockId, Identifier>;
type: Type;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,23 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
mutableOnlyIfOperandsAreMutable: true,
}),
],
[
'flatMap',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
/*
* callee is ConditionallyMutate because items of the array
* flow into the lambda and may be mutated there, even though
* the array object itself is not modified
*/
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Mutable,
noAlias: true,
mutableOnlyIfOperandsAreMutable: true,
}),
],
[
'filter',
addFunction(BUILTIN_SHAPES, [], {
Expand Down Expand Up @@ -534,6 +551,17 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
noAlias: true,
}),
],
[
'flatMap',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Mutable,
noAlias: true,
}),
],
[
'filter',
addFunction(BUILTIN_SHAPES, [], {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export function printPhi(phi: Phi): string {
const items = [];
items.push(printIdentifier(phi.id));
items.push(printMutableRange(phi.id));
items.push(printType(phi.type));
items.push(printType(phi.id.type));
items.push(': phi(');
const phis = [];
for (const [blockId, id] of phi.operands) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,38 @@ export function inferMutableRanges(ir: HIRFunction): void {
// Re-infer mutable ranges for all values
inferMutableLifetimes(ir, true);

// Re-infer mutable ranges for aliases
inferMutableRangesForAlias(ir, aliases);
/**
* The second inferMutableLifetimes() call updates mutable ranges
* of values to account for Store effects. Now we need to update
* all aliases of such values to extend their ranges as well. Note
* that the store only mutates the the directly aliased value and
* not any of its inner captured references. For example:
*
* ```
* let y;
* if (cond) {
* y = [];
* } else {
* y = [{}];
* }
* y.push(z);
* ```
*
* The Store effect from the `y.push` modifies the values that `y`
* directly aliases - the two arrays from the if/else branches -
* but does not modify values that `y` "contains" such as the
* object literal or `z`.
*/
prevAliases = aliases.canonicalize();
while (true) {
inferMutableRangesForAlias(ir, aliases);
inferAliasForPhis(ir, aliases);
const nextAliases = aliases.canonicalize();
if (areEqualMaps(prevAliases, nextAliases)) {
break;
}
prevAliases = nextAliases;
}
}

function areEqualMaps<T>(a: Map<T, T>, b: Map<T, T>): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,6 @@ class SSABuilder {
kind: 'Phi',
id: newId,
operands: predDefs,
type: makeType(),
};

block.phis.add(phi);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
BuiltInArrayId,
BuiltInFunctionId,
BuiltInJsxId,
BuiltInMixedReadonlyId,
BuiltInObjectId,
BuiltInPropsId,
BuiltInRefValueId,
Expand Down Expand Up @@ -68,7 +69,7 @@ export function inferTypes(func: HIRFunction): void {
function apply(func: HIRFunction, unifier: Unifier): void {
for (const [_, block] of func.body.blocks) {
for (const phi of block.phis) {
phi.type = unifier.get(phi.type);
phi.id.type = unifier.get(phi.id.type);
}
for (const instr of block.instructions) {
for (const operand of eachInstructionLValue(instr)) {
Expand Down Expand Up @@ -126,7 +127,7 @@ function* generate(
const returnTypes: Array<Type> = [];
for (const [_, block] of func.body.blocks) {
for (const phi of block.phis) {
yield equation(phi.type, {
yield equation(phi.id.type, {
kind: 'Phi',
operands: [...phi.operands.values()].map(id => id.type),
});
Expand Down Expand Up @@ -483,30 +484,140 @@ class Unifier {
}

if (type.kind === 'Phi') {
const operands = new Set(type.operands.map(i => this.get(i).kind));

CompilerError.invariant(operands.size > 0, {
CompilerError.invariant(type.operands.length > 0, {
reason: 'there should be at least one operand',
description: null,
loc: null,
suggestions: null,
});
const kind = operands.values().next().value;

// there's only one unique type and it's not a type var
if (operands.size === 1 && kind !== 'Type') {
this.unify(v, type.operands[0]);
let candidateType: Type | null = null;
for (const operand of type.operands) {
const resolved = this.get(operand);
if (candidateType === null) {
candidateType = resolved;
} else if (!typeEquals(resolved, candidateType)) {
const unionType = tryUnionTypes(resolved, candidateType);
if (unionType === null) {
candidateType = null;
break;
} else {
candidateType = unionType;
}
} // else same type, continue
}

if (candidateType !== null) {
this.unify(v, candidateType);
return;
}
}

if (this.occursCheck(v, type)) {
const resolvedType = this.tryResolveType(v, type);
if (resolvedType !== null) {
this.substitutions.set(v.id, resolvedType);
return;
}
throw new Error('cycle detected');
}

this.substitutions.set(v.id, type);
}

tryResolveType(v: TypeVar, type: Type): Type | null {
switch (type.kind) {
case 'Phi': {
/**
* Resolve the type of the phi by recursively removing `v` as an operand.
* For example we can end up with types like this:
*
* v = Phi [
* T1
* T2
* Phi [
* T3
* Phi [
* T4
* v <-- cycle!
* ]
* ]
* ]
*
* By recursively removing `v`, we end up with:
*
* v = Phi [
* T1
* T2
* Phi [
* T3
* Phi [
* T4
* ]
* ]
* ]
*
* Which avoids the cycle
*/
const operands = [];
for (const operand of type.operands) {
if (operand.kind === 'Type' && operand.id === v.id) {
continue;
}
const resolved = this.tryResolveType(v, operand);
if (resolved === null) {
return null;
}
operands.push(resolved);
}
return {kind: 'Phi', operands};
}
case 'Type': {
const substitution = this.get(type);
if (substitution !== type) {
const resolved = this.tryResolveType(v, substitution);
if (resolved !== null) {
this.substitutions.set(type.id, resolved);
}
return resolved;
}
return type;
}
case 'Property': {
const objectType = this.tryResolveType(v, this.get(type.objectType));
if (objectType === null) {
return null;
}
return {
kind: 'Property',
objectName: type.objectName,
objectType,
propertyName: type.propertyName,
};
}
case 'Function': {
const returnType = this.tryResolveType(v, this.get(type.return));
if (returnType === null) {
return null;
}
return {
kind: 'Function',
return: returnType,
shapeId: type.shapeId,
};
}
case 'ObjectMethod':
case 'Object':
case 'Primitive':
case 'Poly': {
return type;
}
default: {
assertExhaustive(type, `Unexpected type kind '${(type as any).kind}'`);
}
}
}

occursCheck(v: TypeVar, type: Type): boolean {
if (typeEquals(v, type)) return true;

Expand Down Expand Up @@ -545,3 +656,39 @@ const RefLikeNameRE = /^(?:[a-zA-Z$_][a-zA-Z$_0-9]*)Ref$|^ref$/;
function isRefLikeName(t: PropType): boolean {
return RefLikeNameRE.test(t.objectName) && t.propertyName === 'current';
}

function tryUnionTypes(ty1: Type, ty2: Type): Type | null {
let readonlyType: Type;
let otherType: Type;
if (ty1.kind === 'Object' && ty1.shapeId === BuiltInMixedReadonlyId) {
readonlyType = ty1;
otherType = ty2;
} else if (ty2.kind === 'Object' && ty2.shapeId === BuiltInMixedReadonlyId) {
readonlyType = ty2;
otherType = ty1;
} else {
return null;
}
if (otherType.kind === 'Primitive') {
/**
* Union(Primitive | MixedReadonly) = MixedReadonly
*
* For example, `data ?? null` could return `data`, the fact that RHS
* is a primitive doesn't guarantee the result is a primitive.
*/
return readonlyType;
} else if (
otherType.kind === 'Object' &&
otherType.shapeId === BuiltInArrayId
) {
/**
* Union(Array | MixedReadonly) = Array
*
* In practice this pattern means the result is always an array. Given
* that this behavior requires opting-in to the mixedreadonly type
* (via moduleTypeProvider) this seems like a reasonable heuristic.
*/
return otherType;
}
return null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ export function propagatePhiTypes(fn: HIRFunction): void {
}
if (type !== null) {
phi.id.type = type;
phi.type = type;
propagated.add(phi.id.id);
}
}
Expand Down
Loading

0 comments on commit 130ae88

Please sign in to comment.