Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat(asm): better asm functions support #225

Merged
merged 1 commit into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,22 @@
"default": true,
"description": "Show gas consumption hints for assembly instructions"
},
"tact.hints.showPushcontGas": {
"type": "boolean",
"default": true,
"description": "Show gas consumption hints for PUSHCONT blocks"
},
"tact.hints.gasFormat": {
"type": "string",
"default": ": {gas}",
"description": "Format string for gas consumption hints. Use {gas} as placeholder for the gas value",
"examples": [
"💨 {gas}",
"Gas: {gas}",
"⛽ {gas}",
"{gas} gas units"
]
},
"tact.hints.showExitCodes": {
"type": "boolean",
"default": true,
Expand Down
43 changes: 43 additions & 0 deletions server/src/asm/gas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {AsmInstr} from "@server/psi/Node"

export interface GasConsumption {
value: number
unknown: boolean
exact: boolean
singleInstr: boolean
}

export function computeGasConsumption(instructions: AsmInstr[]): GasConsumption {
const singleInstructionBody = instructions.length === 1

let exact = true
let res = 0

for (const instr of instructions) {
const info = instr.info()
if (!info || info.doc.gas === "") {
exact = false
continue
}
if (info.doc.gas.includes("|") || info.doc.gas.includes("+")) {
exact = false
}
res += Number.parseInt(info.doc.gas)
}

if (!exact && singleInstructionBody) {
return {
value: 0,
unknown: true,
exact: false,
singleInstr: true,
}
}

return {
value: res,
unknown: false,
exact,
singleInstr: singleInstructionBody,
}
}
4 changes: 4 additions & 0 deletions server/src/completion/CompletionContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ export class CompletionContext {
this.inParameter = true
}

if (parent.type === "asm_expression") {
this.isExpression = false
}

if (parent.type === "ERROR" && parent.parent?.type.endsWith("_function")) {
this.isExpression = false
this.isStatement = false
Expand Down
39 changes: 37 additions & 2 deletions server/src/completion/data/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as fs from "node:fs"
import * as path from "node:path"
import {Node as SyntaxNode} from "web-tree-sitter"

export interface AsmInstruction {
mnemonic: string
Expand Down Expand Up @@ -45,9 +46,11 @@ export function asmData(): AsmData {
return data
}

export function findInstruction(name: string): AsmInstruction | null {
export function findInstruction(name: string, args: SyntaxNode[] = []): AsmInstruction | null {
const data = asmData()
const instruction = data.instructions.find(i => i.mnemonic === name)

const realName = adjustName(name, args)
const instruction = data.instructions.find(i => i.mnemonic === realName)
if (instruction) {
return instruction
}
Expand All @@ -62,3 +65,35 @@ export function findInstruction(name: string): AsmInstruction | null {

return null
}

function adjustName(name: string, args: SyntaxNode[]): string {
if (name === "PUSHINT") {
if (args.length === 0) return "PUSHINT_4"

const arg = Number.parseInt(args[0].text)
if (Number.isNaN(arg)) return "PUSHINT_4"

if (arg >= 0 && arg <= 15) return "PUSHINT_4"
if (arg >= -128 && arg <= 127) return "PUSHINT_8"
if (arg >= -32_768 && arg <= 32_767) return "PUSHINT_16"

return "PUSHINT_LONG"
}

if (name === "PUSH") {
if (args.length === 1 && args[0].type === "asm_stack_register") return "PUSH"
if (args.length === 2) return "PUSH2"
if (args.length === 3) return "PUSH3"
return name
}

if (name === "XCHG0") {
return "XCHG_0I"
}

if (name === "XCHG") {
return "XCHG_IJ"
}

return name
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import {CompletionResult, CompletionWeight} from "@server/completion/WeightedCom

export class AsmInstructionCompletionProvider implements CompletionProvider {
public isAvailable(ctx: CompletionContext): boolean {
return ctx.element.node.parent?.type === "tvm_ordinary_word"
return ctx.element.node.type === "tvm_instruction"
}

public addCompletion(_ctx: CompletionContext, result: CompletionResult): void {
const data = asmData()

for (const instruction of data.instructions) {
const name = this.adjustName(instruction.mnemonic)

result.add({
label: instruction.mnemonic,
label: name,
kind: CompletionItemKind.Function,
labelDetails: {
detail: " " + instruction.doc.stack,
Expand All @@ -34,4 +36,13 @@ export class AsmInstructionCompletionProvider implements CompletionProvider {
})
}
}

private adjustName(name: string): string {
if (name.startsWith("PUSHINT_")) return "PUSHINT"
if (name === "XCHG_0I") return "XCHG0"
if (name === "XCHG_IJ") return "XCHG"
if (name === "XCHG_0I_LONG") return "XCHG"
if (name === "XCHG_1I") return "XCHG"
return name
}
}
8 changes: 6 additions & 2 deletions server/src/documentation/asm_documentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ export function generateAsmDoc(word: string): string | null {

const instruction = data.instructions.find(i => i.mnemonic === upperWord)
if (instruction) {
const stackInfo = instruction.doc.stack ? `- Stack: \`${instruction.doc.stack}\`` : ""

const gas = instruction.doc.gas.length > 0 ? instruction.doc.gas : `unknown`
return [
"```",
`${instruction.mnemonic} (${instruction.doc.category})`,
"```",
`- Stack: \`${instruction.doc.stack}\``,
stackInfo,
`- Gas: \`${gas}\``,
"",
instruction.doc.description,
Expand All @@ -31,13 +33,15 @@ export function generateAsmDoc(word: string): string | null {

const alias = data.aliases.find(a => a.mnemonic === upperWord)
if (alias) {
const stackInfo = alias.doc_stack ? `- Stack: \`${alias.doc_stack}\`` : ""

const operandsStr = formatOperands(alias.operands)
return [
"```",
alias.mnemonic,
"```",
`- Alias of: \`${operandsStr} ${alias.alias_of}\`\n`,
alias.doc_stack ? `- Stack: \`${alias.doc_stack}\`\n` : "",
stackInfo,
"",
alias.description ?? "",
"",
Expand Down
11 changes: 8 additions & 3 deletions server/src/e2e/suite/completion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import * as vscode from "vscode"
import * as assert from "node:assert"
import {BaseTestSuite} from "./BaseTestSuite"
import type {TestCase} from "./TestParser"
import {CompletionItem} from "vscode"

suite("Completion Test Suite", () => {
const testSuite = new (class extends BaseTestSuite {
public async getCompletions(
input: string,
triggerCharacter?: string,
): Promise<vscode.CompletionList> {
): Promise<CompletionItem[]> {
const textWithoutCaret = input.replace("<caret>", "")
await this.replaceDocumentText(textWithoutCaret)

Expand All @@ -21,19 +22,23 @@ suite("Completion Test Suite", () => {
this.editor.selection = new vscode.Selection(position, position)
this.editor.revealRange(new vscode.Range(position, position))

return vscode.commands.executeCommand<vscode.CompletionList>(
const items = await vscode.commands.executeCommand<vscode.CompletionList>(
"vscode.executeCompletionItemProvider",
this.document.uri,
position,
triggerCharacter,
)
if (items.items.length > 100) {
return items.items.slice(0, 100)
}
return items.items
}

protected runTest(testFile: string, testCase: TestCase): void {
test(`Completion: ${testCase.name}`, async () => {
const completions = await this.getCompletions(testCase.input, ".")

const items = completions.items.map(item => {
const items = completions.map(item => {
const label = typeof item.label === "object" ? item.label.label : item.label
const details =
(typeof item.label === "object" ? item.label.detail : item.detail) ?? ""
Expand Down
107 changes: 107 additions & 0 deletions server/src/e2e/suite/testcases/completion/asm.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
========================================================================
Completion inside asm function
========================================================================
asm fun test() {
<caret>
}
------------------------------------------------------------------------
2 -ROLLX 18
2 ABS x - |x| 26
2 ACCEPT - 26
2 ADD x y - x+y 18
2 ADDCONST x - x+cc 26
2 ADDDIVMOD x w z - q=floor((x+w)/z) r=(x+w)-zq 26
2 ADDDIVMODC x w y - q=ceil((x+w)/z) r=(x+w)-zq 26
2 ADDDIVMODR x w z - q=round((x+w)/z) r=(x+w)-zq 26
2 ADDRAND x - 26
2 ADDRSHIFTCMOD x w - q=round((x+w)/2^(tt+1)) r=(x+w)-q*2^(tt+1) 34
2 ADDRSHIFTMOD x w z - q=floor((x+w)/2^z) r=(x+w)-q*2^z 26
2 ADDRSHIFTMOD x w - q=floor((x+w)/2^(tt+1)) r=(x+w)-q*2^(tt+1) 34
2 ADDRSHIFTMODC x w z - q=ceil((x+w)/2^z) r=(x+w)-q*2^z 26
2 ADDRSHIFTMODR x w z - q=round((x+w)/2^z) r=(x+w)-q*2^z 26
2 ADDRSHIFTRMOD x w - q=round((x+w)/2^(tt+1)) r=(x+w)-q*2^(tt+1) 34
2 AGAIN c - 18
2 AGAINBRK c - 26
2 AGAINEND - 18
2 AGAINENDBRK - 26
2 AND x y - x&y 18
2 ATEXIT c - 26
2 ATEXITALT c - 26
2 BALANCE - t
2 BBITREFS b - x y 26
2 BBITS b - x 26
2 BCHKBITREFS b x y - 26/76
2 BCHKBITREFSQ b x y - ? 26
2 BCHKBITS b - 34/84
2 BCHKBITSQ b - ? 34
2 BCHKBITSQ_VAR b x - ? 26
2 BCHKBITS_VAR b x - 26/76
2 BCHKREFS b y - 26/76
2 BCHKREFSQ b y - ? 26
2 BDEPTH b - x 26
2 BITSIZE x - c 26
2 BLESS s - c 26
2 BLESSARGS x_1...x_r s - c 26
2 BLESSNUMARGSs - c
2 BLESSVARARGS x_1...x_r s r n - c 26+s''
2 BLKDROP 26
2 BLKDROP2 26
2 BLKPUSH 26
2 BLKSWAP 26
2 BLKSWX 18
2 BLOCKLT - x
2 BLS_AGGREGATE sig_1 ... sig_n n - sig n*4350-2616
2 BLS_AGGREGATEVERIFY pk_1 msg_1 ... pk_n msg_n n sgn - bool 38534+n*22500
2 BLS_FASTAGGREGATEVERIFY pk_1 ... pk_n n msg sig - bool 58034+n*3000
2 BLS_G1_ADD x y - x+y 3934
2 BLS_G1_INGROUP x - bool 2984
2 BLS_G1_ISZERO x - bool 34
2 BLS_G1_MUL x s - x*s 5234
2 BLS_G1_MULTIEXP x_1 s_1 ... x_n s_n n - x_1*s_1+...+x_n*s_n 11409+n*630+n/floor(max(log2(n),4))*8820
2 BLS_G1_NEG x - -x 784
2 BLS_G1_SUB x y - x-y 3934
2 BLS_G1_ZERO - zero 34
2 BLS_G2_ADD x y - x+y 6134
2 BLS_G2_INGROUP x - bool 4284
2 BLS_G2_ISZERO x - bool 34
2 BLS_G2_MUL x s - x*s 10584
2 BLS_G2_MULTIEXP x_1 s_1 ... x_n s_n n - x_1*s_1+...+x_n*s_n 30422+n*1280+n/floor(max(log2(n),4))*22840
2 BLS_G2_NEG x - -x 1584
2 BLS_G2_SUB x y - x-y 6134
2 BLS_G2_ZERO - zero 34
2 BLS_MAP_TO_G1 f - x 2384
2 BLS_MAP_TO_G2 f - x 7984
2 BLS_PAIRING x_1 y_1 ... x_n y_n n - bool 20034+n*11800
2 BLS_PUSHR - r 34
2 BLS_VERIFY pk msg sgn - bool 61034
2 BOOLEVAL c - ? 26
2 BRANCH f - 26
2 BREFS b - y 26
2 BREMBITREFS b - x' y' 26
2 BREMBITS b - x' 26
2 BREMREFS b - y' 26
2 CADDRt - x
2 CADRt - x
2 CALLCC c - 26
2 CALLCCARGS c - 34
2 CALLCCVARARGS c p r - 26
2 CALLDICT - nn
2 CALLDICT_LONG - n
2 CALLREF 126/51
2 CALLXARGS c - 26
2 CALLXARGS_VAR c - 26
2 CALLXVARARGS c p r - 26
2 CDATASIZE c n - x y z
2 CDATASIZEQ c n - x y z -1 or 0
2 CDDDRt - x
2 CDDRt - x
2 CDEPTH c - x 26
2 CDEPTHI cell - depth 26
2 CDEPTHIX cell i - depth 26
2 CHANGELIB h x - 526
2 CHASHI cell - hash 26
2 CHASHIX cell i - hash 26
2 CHKBITx - x
2 CHKBOOLx - x
2 CHKDEPTH i - 18/58
2 CHKNAN x - x 18/68
30 changes: 30 additions & 0 deletions server/src/e2e/suite/testcases/documentation/asm.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
========================================================================
PUSHINT instruction
========================================================================
primitive Int;

asm fun foo() { 10 <caret>PUSHINT }
------------------------------------------------------------------------
```
PUSHINT_4 (const_int)
```
- Stack: `- x`
- Gas: `18`

Pushes integer `x` into the stack. `-5 <= x <= 10`.
Here `i` equals four lower-order bits of `x` (`i=x mod 16`).

========================================================================
ENDC instruction
========================================================================
primitive Int;

asm fun foo() { <caret>ENDC }
------------------------------------------------------------------------
```
ENDC (cell_build)
```
- Stack: `b - c`
- Gas: `518`

Converts a _Builder_ into an ordinary _Cell_.
Loading