Skip to content

Commit

Permalink
Merge pull request #83 from algorandfoundation/feat/extract-op-nuances
Browse files Browse the repository at this point in the history
feat: Improve devex of `op.extract` by supporting 'extract to end' be…
  • Loading branch information
tristanmenzel authored Jan 17, 2025
2 parents a783891 + 361f06c commit 479cf70
Show file tree
Hide file tree
Showing 17 changed files with 15,188 additions and 33 deletions.
13,545 changes: 13,545 additions & 0 deletions op-module.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"dev:examples": "tsx src/cli.ts build examples --output-awst --output-awst-json",
"dev:approvals": "rimraf tests/approvals/out && tsx src/cli.ts build tests/approvals --dry-run",
"dev:expected-output": "tsx src/cli.ts build tests/expected-output --dry-run",
"dev:testing": "tsx src/cli.ts build tests/approvals/multi-inheritance.algo.ts --output-awst --output-awst-json --output-ssa-ir --log-level=info --out-dir out/[name] --optimization-level=0",
"dev:testing": "tsx src/cli.ts build tests/approvals/extract-bytes.algo.ts --output-awst --output-awst-json --output-ssa-ir --log-level=info --out-dir out/[name] --optimization-level=0",
"audit": "better-npm-audit audit",
"format": "prettier --write .",
"lint": "eslint \"src/**/*.ts\"",
Expand Down
2 changes: 1 addition & 1 deletion packages/algo-ts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@algorandfoundation/algorand-typescript",
"version": "1.0.0-beta.6",
"version": "1.0.0-beta.7",
"description": "This package contains definitions for the types which comprise Algorand TypeScript which can be compiled to run on the Algorand Virtual Machine using the Puya compiler.",
"private": false,
"main": "index.js",
Expand Down
11 changes: 2 additions & 9 deletions packages/algo-ts/src/op-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -660,14 +660,6 @@ export type ExpType = (a: uint64, b: uint64) => uint64
*/
export type ExpwType = (a: uint64, b: uint64) => readonly [uint64, uint64]

/**
* A range of bytes from A starting at B up to but not including B+C. If B+C is larger than the array length, the program fails
* `extract3` can be called using `extract` with no immediates.
* @see Native TEAL opcode: [`extract3`](https://developer.algorand.org/docs/get-details/dapps/avm/teal/opcodes/v10/#extract3)
* Min AVM version: 5
*/
export type ExtractType = (a: bytes, b: uint64, c: uint64) => bytes

/**
* A uint16 formed from a range of big-endian bytes from A starting at B up to but not including B+2. If B+2 is larger than the array length, the program fails
* @see Native TEAL opcode: [`extract_uint16`](https://developer.algorand.org/docs/get-details/dapps/avm/teal/opcodes/v10/#extract_uint16)
Expand Down Expand Up @@ -3025,6 +3017,7 @@ export type VoterParamsType = {
* Min AVM version: 7
*/
export type VrfVerifyType = (s: VrfVerify, a: bytes, b: bytes, c: bytes) => readonly [bytes, boolean]
export type ExtractType = ((a: bytes, b: uint64) => bytes) & ((a: bytes, b: uint64, c: uint64) => bytes)
export type SelectType = ((a: bytes, b: bytes, c: uint64) => bytes) & ((a: uint64, b: uint64, c: uint64) => uint64)
export type SetBitType = ((target: bytes, n: uint64, c: uint64) => bytes) & ((target: uint64, n: uint64, c: uint64) => uint64)
export type OpsNamespace = {
Expand Down Expand Up @@ -3056,7 +3049,6 @@ export type OpsNamespace = {
ed25519verifyBare: Ed25519verifyBareType
exp: ExpType
expw: ExpwType
extract: ExtractType
extractUint16: ExtractUint16Type
extractUint32: ExtractUint32Type
extractUint64: ExtractUint64Type
Expand Down Expand Up @@ -3092,6 +3084,7 @@ export type OpsNamespace = {
Txn: TxnType
VoterParams: VoterParamsType
vrfVerify: VrfVerifyType
extract: ExtractType
select: SelectType
setBit: SetBitType
}
2 changes: 1 addition & 1 deletion packages/algo-ts/src/op.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const ed25519verifyBare: Ed25519verifyBareType = createFunctionProxy('ed2
export const ed25519verify: Ed25519verifyType = createFunctionProxy('ed25519verify')
export const exp: ExpType = createFunctionProxy('exp')
export const expw: ExpwType = createFunctionProxy('expw')
export const extract: ExtractType = createFunctionProxy('extract')
export const extract: ExtractType = createFunctionProxy('extract') as ExtractType
export const extractUint16: ExtractUint16Type = createFunctionProxy('extractUint16')
export const extractUint32: ExtractUint32Type = createFunctionProxy('extractUint32')
export const extractUint64: ExtractUint64Type = createFunctionProxy('extractUint64')
Expand Down
46 changes: 45 additions & 1 deletion scripts/build-op-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const EXCLUDED_OPCODES = new Set([
'replace2',
'substring',
'extract',
'extract3',
'gaid',
'gload',
'gloads',
Expand Down Expand Up @@ -207,7 +208,6 @@ const RENAMED_OPCODES_MAP = new Map([
['return', 'exit'],
['replace3', 'replace'],
['substring3', 'substring'],
['extract3', 'extract'],
['gaids', 'gaid'],
['gloadss', 'gload'],
['setbit', 'setBit'],
Expand Down Expand Up @@ -470,6 +470,50 @@ export function buildOpModule() {
}

// Manually handle select overloads
opModule.items.push({
type: 'op-overloaded-function',
name: 'extract',
minAvmVersion: 5,
signatures: [
{
stackArgs: [
{
name: 'a',
type: AlgoTsType.Bytes,
},
{
name: 'b',
type: AlgoTsType.Uint64,
},
],
immediateArgs: [],
returnTypes: [AlgoTsType.Bytes],
docs: ['A range of bytes from A starting at B up to the end of the sequence'],
},
{
stackArgs: [
{
name: 'a',
type: AlgoTsType.Bytes,
},
{
name: 'b',
type: AlgoTsType.Uint64,
},
{
name: 'c',
type: AlgoTsType.Uint64,
},
],
immediateArgs: [],
returnTypes: [AlgoTsType.Bytes],
docs: [
'A range of bytes from A starting at B up to but not including B+C. If B+C is larger than the array length, the program fails',
],
},
],
opCode: 'extract3',
})
opModule.items.push({
type: 'op-overloaded-function',
name: 'select',
Expand Down
93 changes: 89 additions & 4 deletions src/awst_build/eb/op-module-builder.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { intrinsicFactory } from '../../awst/intrinsic-factory'
import { nodeFactory } from '../../awst/node-factory'
import { Expression, IntegerConstant, StringConstant } from '../../awst/nodes'
import { Expression, IntegerConstant, StringConstant, UInt64BinaryOperator } from '../../awst/nodes'
import type { SourceLocation } from '../../awst/source-location'
import { CodeError, InternalError } from '../../errors'
import { enumerate, invariant } from '../../util'
import { codeInvariant, enumerate, invariant } from '../../util'
import type { IntrinsicOpGrouping, IntrinsicOpMapping } from '../op-metadata'
import { OP_METADATA } from '../op-metadata'
import type { PType } from '../ptypes'
import { IntrinsicEnumType, IntrinsicFunctionGroupType, IntrinsicFunctionType, stringPType, voidPType } from '../ptypes'
import { typeRegistry } from '../type-registry'
import {
bytesPType,
IntrinsicEnumType,
IntrinsicFunctionGroupType,
IntrinsicFunctionType,
stringPType,
uint64PType,
voidPType,
} from '../ptypes'
import { instanceEb, typeRegistry } from '../type-registry'
import { FunctionBuilder, InstanceExpressionBuilder, NodeBuilder } from './index'
import { requestConstantOfType, requestExpressionOfType } from './util'
import { parseFunctionArgs } from './util/arg-parsing'

export class IntrinsicOpGroupBuilder extends NodeBuilder {
private opGrouping: IntrinsicOpGrouping
Expand Down Expand Up @@ -72,7 +82,82 @@ abstract class IntrinsicOpBuilderBase extends FunctionBuilder {
super(sourceLocation)
}

/**
* Extract with 2 args extracts to the end of the sequence, the exact op code depends on if the start index is a constant value or not
* @param args
* @param typeArgs
* @param sourceLocation
*/
handleExtract(args: ReadonlyArray<NodeBuilder>, typeArgs: ReadonlyArray<PType>, sourceLocation: SourceLocation): NodeBuilder {
const {
args: [target, start, end],
} = parseFunctionArgs({
args,
typeArgs,
genericTypeArgs: 0,
funcName: 'extract',
callLocation: sourceLocation,
argSpec: (a) => [a.required(bytesPType), a.required(uint64PType), a.optional(uint64PType)],
})
if (end) {
const endExpr = end.resolve()
codeInvariant(
!(endExpr instanceof IntegerConstant && endExpr.value === 0n),
'Extract with length=0 will always return an empty byte array. Omit length parameter to extract to the end of the sequence.',
)
return instanceEb(
nodeFactory.intrinsicCall({
opCode: 'extract3',
immediates: [],
stackArgs: [target.resolve(), start.resolve(), endExpr],
wtype: bytesPType.wtype,
sourceLocation,
}),
bytesPType,
)
}
const startExpr = start.resolve()
if (startExpr instanceof IntegerConstant) {
// Use immediate version of extract
return instanceEb(
nodeFactory.intrinsicCall({
opCode: 'extract',
immediates: [startExpr.value, 0n],
stackArgs: [target.resolve()],
wtype: bytesPType.wtype,
sourceLocation,
}),
bytesPType,
)
} else {
const targetExpr = target.singleEvaluation().resolve()
const startExpr = start.singleEvaluation().resolve()
return instanceEb(
nodeFactory.intrinsicCall({
opCode: 'extract3',
immediates: [],
stackArgs: [
targetExpr,
startExpr,
nodeFactory.uInt64BinaryOperation({
op: UInt64BinaryOperator.sub,
sourceLocation,
left: intrinsicFactory.bytesLen({ value: targetExpr, sourceLocation }),
right: startExpr,
}),
],
wtype: bytesPType.wtype,
sourceLocation,
}),
bytesPType,
)
}
}

call(args: ReadonlyArray<NodeBuilder>, typeArgs: ReadonlyArray<PType>, sourceLocation: SourceLocation): NodeBuilder {
if (this.opMapping.op === 'extract3') {
return this.handleExtract(args, typeArgs, sourceLocation)
}
signatureLoop: for (const [index, sig] of enumerate(this.opMapping.signatures)) {
const isLastSig = index + 1 >= this.opMapping.signatures.length
if (args.length !== sig.argNames.length) {
Expand Down
41 changes: 25 additions & 16 deletions src/awst_build/op-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1285,22 +1285,6 @@ export const OP_METADATA: Record<string, IntrinsicOpMapping | IntrinsicOpGroupin
},
],
},
extract: {
type: 'op-mapping',
op: 'extract3',
signatures: [
{
argNames: ['a', 'b', 'c'],
immediateArgs: [],
stackArgs: [
{ name: 'a', ptypes: [ptypes.bytesPType] },
{ name: 'b', ptypes: [ptypes.uint64PType] },
{ name: 'c', ptypes: [ptypes.uint64PType] },
],
returnType: ptypes.bytesPType,
},
],
},
extractUint16: {
type: 'op-mapping',
op: 'extract_uint16',
Expand Down Expand Up @@ -4936,6 +4920,31 @@ export const OP_METADATA: Record<string, IntrinsicOpMapping | IntrinsicOpGroupin
},
],
},
extract: {
type: 'op-mapping',
op: 'extract3',
signatures: [
{
argNames: ['a', 'b'],
immediateArgs: [],
stackArgs: [
{ name: 'a', ptypes: [ptypes.bytesPType] },
{ name: 'b', ptypes: [ptypes.uint64PType] },
],
returnType: ptypes.bytesPType,
},
{
argNames: ['a', 'b', 'c'],
immediateArgs: [],
stackArgs: [
{ name: 'a', ptypes: [ptypes.bytesPType] },
{ name: 'b', ptypes: [ptypes.uint64PType] },
{ name: 'c', ptypes: [ptypes.uint64PType] },
],
returnType: ptypes.bytesPType,
},
],
},
select: {
type: 'op-mapping',
op: 'select',
Expand Down
23 changes: 23 additions & 0 deletions tests/approvals/extract-bytes.algo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { uint64 } from '@algorandfoundation/algorand-typescript'
import { assert, BaseContract, Bytes } from '@algorandfoundation/algorand-typescript'
import { extract } from '@algorandfoundation/algorand-typescript/op'

export class ExtractBytesAlgo extends BaseContract {
approvalProgram(): boolean {
this.test(2, 0)
return true
}

private test(two: uint64, zero: uint64) {
assert(two === 2, 'Param two should be 2')
assert(zero === 0, 'Param zero should be 0')
const b = Bytes('abcdefg')
assert(extract(b, 2) === Bytes('cdefg'))
assert(extract(b, two) === Bytes('cdefg'))

assert(extract(b, 2, 2) === Bytes('cd'))
assert(extract(b, two, two) === Bytes('cd'))

assert(extract(b, two, zero) === Bytes(''))
}
}
Loading

0 comments on commit 479cf70

Please sign in to comment.