Skip to content

Commit 5575a5a

Browse files
authored
Merge pull request #8548 from processing/strands-boolean-handling
Handle booleans used as temp variables in p5.strands
2 parents 5357b21 + aa2b859 commit 5575a5a

10 files changed

Lines changed: 3431 additions & 2991 deletions

File tree

docs/parameterData.json

Lines changed: 2158 additions & 2134 deletions
Large diffs are not rendered by default.

src/strands/ir_builders.js

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as DAG from './ir_dag'
22
import * as CFG from './ir_cfg'
33
import * as FES from './strands_FES'
4-
import { NodeType, OpCode, BaseType, DataType, BasePriority, OpCodeToSymbol, typeEquals, } from './ir_types';
4+
import { NodeType, OpCode, BaseType, DataType, BasePriority, OpCodeToSymbol, typeEquals, booleanOpCode } from './ir_types';
55
import { createStrandsNode, StrandsNode } from './strands_node';
66
import { strandsBuiltinFunctions } from './strands_builtins';
77

@@ -50,12 +50,22 @@ export function unaryOpNode(strandsContext, nodeOrValue, opCode) {
5050
node = createStrandsNode(id, dimension, strandsContext);
5151
}
5252
dependsOn = [node.id];
53+
54+
const typeInfo = {
55+
baseType: dag.baseTypes[node.id],
56+
dimension: node.dimension
57+
};
58+
if (booleanOpCode[opCode]) {
59+
typeInfo.baseType = BaseType.BOOL;
60+
typeInfo.dimension = 1;
61+
}
62+
5363
const nodeData = DAG.createNodeData({
5464
nodeType: NodeType.OPERATION,
5565
opCode,
5666
dependsOn,
57-
baseType: dag.baseTypes[node.id],
58-
dimension: node.dimension
67+
baseType: typeInfo.baseType,
68+
dimension: typeInfo.dimension
5969
})
6070
const id = DAG.getOrCreateNode(dag, nodeData);
6171
CFG.recordInBasicBlock(cfg, cfg.currentBlock, id);
@@ -137,6 +147,11 @@ export function binaryOpNode(strandsContext, leftStrandsNode, rightArg, opCode)
137147
}
138148
}
139149

150+
if (booleanOpCode[opCode]) {
151+
cast.toType.baseType = BaseType.BOOL;
152+
cast.toType.dimension = 1;
153+
}
154+
140155
const nodeData = DAG.createNodeData({
141156
nodeType: NodeType.OPERATION,
142157
opCode,
@@ -224,6 +239,17 @@ function mapPrimitiveDepsToIDs(strandsContext, typeInfo, dependsOn) {
224239
calculatedDimensions += dimension;
225240
continue;
226241
}
242+
else if (typeof dep === 'boolean') {
243+
// Handle boolean literals - convert to bool type
244+
const { id, dimension } = scalarLiteralNode(strandsContext, { dimension: 1, baseType: BaseType.BOOL }, dep);
245+
mappedDependencies.push(id);
246+
calculatedDimensions += dimension;
247+
// Update baseType to BOOL if it was inferred
248+
if (baseType !== BaseType.BOOL) {
249+
baseType = BaseType.BOOL;
250+
}
251+
continue;
252+
}
227253
else {
228254
FES.userError('type error', `You've tried to construct a scalar or vector type with a non-numeric value: ${dep}`);
229255
}
@@ -274,7 +300,12 @@ export function primitiveConstructorNode(strandsContext, typeInfo, dependsOn) {
274300
const { mappedDependencies, inferredTypeInfo } = mapPrimitiveDepsToIDs(strandsContext, typeInfo, dependsOn);
275301

276302
const finalType = {
277-
baseType: typeInfo.baseType,
303+
// We might have inferred a non numeric type. Currently this is
304+
// just used for booleans. Maybe this needs to be something more robust
305+
// if we ever want to support inference of e.g. int vectors?
306+
baseType: inferredTypeInfo.baseType === BaseType.BOOL
307+
? BaseType.BOOL
308+
: typeInfo.baseType,
278309
dimension: inferredTypeInfo.dimension
279310
};
280311

src/strands/ir_types.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const BaseType = {
3737
BOOL: "bool",
3838
MAT: "mat",
3939
DEFER: "defer",
40+
ASSIGN_ON_USE: "assign_on_use",
4041
SAMPLER2D: "sampler2D",
4142
SAMPLER: "sampler",
4243
};
@@ -46,6 +47,7 @@ export const BasePriority = {
4647
[BaseType.BOOL]: 1,
4748
[BaseType.MAT]: 0,
4849
[BaseType.DEFER]: -1,
50+
[BaseType.ASSIGN_ON_USE]: -2,
4951
[BaseType.SAMPLER2D]: -10,
5052
[BaseType.SAMPLER]: -11,
5153
};
@@ -66,6 +68,7 @@ export const DataType = {
6668
mat3: { fnName: "mat3x3", baseType: BaseType.MAT, dimension:3, priority: 0, },
6769
mat4: { fnName: "mat4x4", baseType: BaseType.MAT, dimension:4, priority: 0, },
6870
defer: { fnName: null, baseType: BaseType.DEFER, dimension: null, priority: -1 },
71+
assign_on_use: { fnName: null, baseType: BaseType.ASSIGN_ON_USE, dimension: null, priority: -2 },
6972
sampler2D: { fnName: "sampler2D", baseType: BaseType.SAMPLER2D, dimension: 1, priority: -10 },
7073
sampler: { fnName: "sampler", baseType: BaseType.SAMPLER, dimension: 1, priority: -11 },
7174
}
@@ -137,22 +140,22 @@ export const OpCode = {
137140
}
138141
};
139142
export const OperatorTable = [
140-
{ arity: "unary", name: "not", symbol: "!", opCode: OpCode.Unary.LOGICAL_NOT },
143+
{ arity: "unary", boolean: true, name: "not", symbol: "!", opCode: OpCode.Unary.LOGICAL_NOT },
141144
{ arity: "unary", name: "neg", symbol: "-", opCode: OpCode.Unary.NEGATE },
142145
{ arity: "unary", name: "plus", symbol: "+", opCode: OpCode.Unary.PLUS },
143146
{ arity: "binary", name: "add", symbol: "+", opCode: OpCode.Binary.ADD },
144147
{ arity: "binary", name: "sub", symbol: "-", opCode: OpCode.Binary.SUBTRACT },
145148
{ arity: "binary", name: "mult", symbol: "*", opCode: OpCode.Binary.MULTIPLY },
146149
{ arity: "binary", name: "div", symbol: "/", opCode: OpCode.Binary.DIVIDE },
147150
{ arity: "binary", name: "mod", symbol: "%", opCode: OpCode.Binary.MODULO },
148-
{ arity: "binary", name: "equalTo", symbol: "==", opCode: OpCode.Binary.EQUAL },
149-
{ arity: "binary", name: "notEqual", symbol: "!=", opCode: OpCode.Binary.NOT_EQUAL },
150-
{ arity: "binary", name: "greaterThan", symbol: ">", opCode: OpCode.Binary.GREATER_THAN },
151-
{ arity: "binary", name: "greaterEqual", symbol: ">=", opCode: OpCode.Binary.GREATER_EQUAL },
152-
{ arity: "binary", name: "lessThan", symbol: "<", opCode: OpCode.Binary.LESS_THAN },
153-
{ arity: "binary", name: "lessEqual", symbol: "<=", opCode: OpCode.Binary.LESS_EQUAL },
154-
{ arity: "binary", name: "and", symbol: "&&", opCode: OpCode.Binary.LOGICAL_AND },
155-
{ arity: "binary", name: "or", symbol: "||", opCode: OpCode.Binary.LOGICAL_OR },
151+
{ arity: "binary", boolean: true, name: "equalTo", symbol: "==", opCode: OpCode.Binary.EQUAL },
152+
{ arity: "binary", boolean: true, name: "notEqual", symbol: "!=", opCode: OpCode.Binary.NOT_EQUAL },
153+
{ arity: "binary", boolean: true, name: "greaterThan", symbol: ">", opCode: OpCode.Binary.GREATER_THAN },
154+
{ arity: "binary", boolean: true, name: "greaterEqual", symbol: ">=", opCode: OpCode.Binary.GREATER_EQUAL },
155+
{ arity: "binary", boolean: true, name: "lessThan", symbol: "<", opCode: OpCode.Binary.LESS_THAN },
156+
{ arity: "binary", boolean: true, name: "lessEqual", symbol: "<=", opCode: OpCode.Binary.LESS_EQUAL },
157+
{ arity: "binary", boolean: true, name: "and", symbol: "&&", opCode: OpCode.Binary.LOGICAL_AND },
158+
{ arity: "binary", boolean: true, name: "or", symbol: "||", opCode: OpCode.Binary.LOGICAL_OR },
156159
];
157160
export const ConstantFolding = {
158161
[OpCode.Binary.ADD]: (a, b) => a + b,
@@ -173,7 +176,8 @@ export const ConstantFolding = {
173176
export const OpCodeToSymbol = {};
174177
export const UnarySymbolToName = {};
175178
export const BinarySymbolToName = {};
176-
for (const { symbol, opCode, name, arity } of OperatorTable) {
179+
export const booleanOpCode = {};
180+
for (const { symbol, opCode, name, arity, boolean } of OperatorTable) {
177181
// SymbolToOpCode[symbol] = opCode;
178182
OpCodeToSymbol[opCode] = symbol;
179183
if (arity === 'unary') {
@@ -182,6 +186,9 @@ for (const { symbol, opCode, name, arity } of OperatorTable) {
182186
if (arity === 'binary') {
183187
BinarySymbolToName[symbol] = name;
184188
}
189+
if (boolean) {
190+
booleanOpCode[opCode] = true;
191+
}
185192
}
186193
export const BlockType = {
187194
GLOBAL: 'global',

src/strands/strands_api.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,20 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
198198
if (args.length > 4) {
199199
FES.userError("type error", "It looks like you've tried to construct a p5.strands node implicitly, with more than 4 components. This is currently not supported.")
200200
}
201-
const { id, dimension } = build.primitiveConstructorNode(strandsContext, { baseType: BaseType.FLOAT, dimension: null }, args.flat());
201+
// Filter out undefined/null values
202+
const flatArgs = args.flat();
203+
const definedArgs = flatArgs.filter(a => a !== undefined && a !== null);
204+
205+
// If all args are undefined, this is likely a `let myVar` at the
206+
// start of an if statement and it will be assigned within the branches.
207+
// For that, we use an assign-on-use node, meaning we'll take the type of the
208+
// values assigned to it.
209+
if (definedArgs.length === 0) {
210+
const { id, dimension } = build.primitiveConstructorNode(strandsContext, { baseType: BaseType.ASSIGN_ON_USE, dimension: null }, [0]);
211+
return createStrandsNode(id, dimension, strandsContext);
212+
}
213+
214+
const { id, dimension } = build.primitiveConstructorNode(strandsContext, { baseType: BaseType.FLOAT, dimension: null }, definedArgs);
202215
return createStrandsNode(id, dimension, strandsContext);//new StrandsNode(id, dimension, strandsContext);
203216
}
204217
//////////////////////////////////////////////
@@ -337,7 +350,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
337350
// variant or also one more directly translated from GLSL, or to be more compatible with
338351
// APIs we documented at the release of 2.x and have to continue supporting.
339352
for (const type in DataType) {
340-
if (type === BaseType.DEFER || type === 'sampler') {
353+
if (type === BaseType.DEFER || type === BaseType.ASSIGN_ON_USE || type === 'sampler') {
341354
continue;
342355
}
343356
const typeInfo = DataType[type];

src/strands/strands_phi_utils.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
import * as CFG from './ir_cfg';
22
import * as DAG from './ir_dag';
3-
import { NodeType } from './ir_types';
3+
import { NodeType, BaseType } from './ir_types';
44

55
export function createPhiNode(strandsContext, phiInputs, varName) {
66
// Determine the proper dimension and baseType from the inputs
77
const validInputs = phiInputs.filter(input => input.value.id !== null);
88
if (validInputs.length === 0) {
99
throw new Error(`No valid inputs for phi node for variable ${varName}`);
1010
}
11-
// Get dimension and baseType from first valid input
12-
let firstInput = validInputs
13-
.map((input) => DAG.getNodeDataFromID(strandsContext.dag, input.value.id))
14-
.find((input) => input.dimension) ??
15-
DAG.getNodeDataFromID(strandsContext.dag, validInputs[0].value.id);
11+
12+
// Get dimension and baseType from first valid input, skipping ASSIGN_ON_USE nodes
13+
const inputNodes = validInputs.map((input) => DAG.getNodeDataFromID(strandsContext.dag, input.value.id));
14+
let firstInput = inputNodes.find((input) => input.baseType !== BaseType.ASSIGN_ON_USE && input.dimension) ??
15+
inputNodes.find((input) => input.baseType !== BaseType.ASSIGN_ON_USE) ??
16+
inputNodes[0];
17+
1618
const dimension = firstInput.dimension;
1719
const baseType = firstInput.baseType;
20+
1821
const nodeData = {
1922
nodeType: NodeType.PHI,
2023
dimension,

0 commit comments

Comments
 (0)