Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Goal: Source mapping PC to TEAL #3726

Merged
merged 10 commits into from
Apr 20, 2022

Conversation

algochoi
Copy link
Contributor

@algochoi algochoi commented Mar 8, 2022

Summary

This is a PR generates a PC to TEAL source map file when goal clerk compile with the -m option is invoked.

Mostly shamelessly taken from this PR.

The source map follows this spec used in JS. The mappings are encoded using base64 VLQ and should be compatible with most browsers, including the tealdbg on CDT.

Example

myprog.teal

#pragma version 5

int 3
byte 0x03
btoi
==
assert

int 10
byte 0x10
btoi
==
assert

myprog.teal.map

{
  "version":3,
  "file":"",
  "sourceRoot":"",
  "sources":["myprog.teal"],
  "names":[],
  "mapping":";AAEA;;AAGA;;;AAIA;AAKA;AAMA;AAQA;;AASA;;;AAUA;AAWA;AAYA"
}

Example decoder here: https://github.com/algochoi/teal-sourcemap-decoder

myprog_annotated.teal

#pragma version 5

int 3            // PC: 1
byte 0x03        // PC: 3
btoi             // PC: 6
==               // PC: 7
assert           // PC: 8

int 10           // PC: 9
byte 0x10        // PC: 11
btoi             // PC: 14
==               // PC: 15
assert           // PC: 16

Test Plan

This also changes the goal clerk compile command, so any manual changes to documentation needs to be done as well.

@codecov-commenter
Copy link

codecov-commenter commented Mar 17, 2022

Codecov Report

Merging #3726 (21aa4a1) into master (3dd1b6f) will increase coverage by 0.02%.
The diff coverage is 70.45%.

@@            Coverage Diff             @@
##           master    #3726      +/-   ##
==========================================
+ Coverage   50.10%   50.12%   +0.02%     
==========================================
  Files         393      394       +1     
  Lines       68382    68409      +27     
==========================================
+ Hits        34263    34292      +29     
+ Misses      30420    30413       -7     
- Partials     3699     3704       +5     
Impacted Files Coverage Δ
cmd/tealdbg/util.go 84.61% <ø> (-5.13%) ⬇️
cmd/goal/clerk.go 9.01% <7.14%> (-0.03%) ⬇️
cmd/tealdbg/debugger.go 72.69% <100.00%> (-0.81%) ⬇️
data/transactions/logic/sourcemap.go 100.00% <100.00%> (ø)
ledger/blockqueue.go 82.18% <0.00%> (-2.88%) ⬇️
data/transactions/verify/txn.go 44.15% <0.00%> (-0.87%) ⬇️
ledger/acctupdates.go 68.51% <0.00%> (-0.66%) ⬇️
catchup/service.go 69.38% <0.00%> (-0.25%) ⬇️
network/wsPeer.go 68.05% <0.00%> (ø)
... and 6 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3dd1b6f...21aa4a1. Read the comment docs.

@algochoi algochoi changed the title Source mapping: PC to TEAL Draft Source mapping: PC to TEAL Apr 14, 2022
@@ -0,0 +1,94 @@
// Copyright (C) 2019-2022 Algorand, Inc.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also thought of putting this file in the tealdbg package, but I don't think it can be exported from there.

@algochoi algochoi marked this pull request as ready for review April 14, 2022 20:24
Copy link
Contributor

@algoidurovic algoidurovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! As you can see, all my suggestions are nits so feel free to resolve anything you don't think is worthwhile to address.

// SourceMap contains details from the source to assembly process
// currently contains map of TEAL source line to assembled bytecode position
// and details about the template variables contained in the source file
type SourceMap struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I had to check the spec for some of the fields here so it might be useful to describe the fields in a comment.

Spec:
"Line 1: The entire file is a single JSON object
Line 2: File version (always the first entry in the object) and must be a positive integer.
Line 3: An optional name of the generated code that this source map is associated with.
Line 4: An optional source root, useful for relocating source files on a server or removing repeated values in the “sources” entry. This value is prepended to the individual entries in the “source” field.
Line 5: A list of original sources used by the “mappings” entry.
Line 6: A list of symbol names used by the “mappings” entry.
Line 7: A string with the encoded mapping data in base64 VLQ encoding. Each semicolon separates a line in the generated code. Note that multiple lines in the generated code may point to the same line in the source code."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point - I eventually settled on linking the sourcemap specs URL instead in the comments, so the interested user can look at the original doc in more detail. Linking to URLs also might

type SourceMap struct {
Version int `json:"version"`
File string `json:"file"`
SourceRoot string `json:"sourceRoot"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we might want to make some of these fields optional in the json serialization depending on how closely we want to follow the spec.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, added omitempty field for File and SourceRoot

@@ -123,6 +125,7 @@ func init() {

compileCmd.Flags().BoolVarP(&disassemble, "disassemble", "D", false, "disassemble a compiled program")
compileCmd.Flags().BoolVarP(&noProgramOutput, "no-out", "n", false, "don't write contract program binary")
compileCmd.Flags().BoolVarP(&writeSourceMap, "map", "m", false, "write out assembly map")
Copy link
Contributor

@algoidurovic algoidurovic Apr 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think it'd be nice if the source map format version was included here or somewhere else in the documentation easily accessible to the user.

"github.com/stretchr/testify/require"
)

func TestGetSourceMap(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this test doesn't cover the (unlikely) edge case where the line number is large enough that a left shift turns it negative.

@michaeldiamant michaeldiamant self-requested a review April 14, 2022 21:40
@barnjamin
Copy link
Contributor

Looks pretty good to me, I'd be interested in a follow up pr to have the compile -D accept a source map as input as well as the binary to reproduce the original. That might require saving original labels and comments though which I guess would be part of the Extensions bit of the spec.

barnjamin
barnjamin previously approved these changes Apr 15, 2022
@michaeldiamant michaeldiamant merged commit 0b59e09 into algorand:master Apr 20, 2022
@@ -1056,6 +1069,17 @@ var compileCmd = &cobra.Command{
reportErrorf("%s: %s", outname, err)
}
}
if writeSourceMap {
mapname := fname + ".map"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@algochoi small question/request, was there a specific reason you chose fname + ".map" for the map file name? With this the source map is given the name of the input file, which I think is less intuitive/accurate.

In JS it's common practice for the map file to be named after the produced output file, since that's the starting point from which you can go "backwards" with the map, so I was expecting outname + ".map" here (idk what should happen when the output is stdout, maybe an error?).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this the source map is given the name of the input file, which I think is less intuitive/accurate.

That is a fair point, and I can see the confusion. I can open up a PR to fix this.

idk what should happen when the output is stdout, maybe an error?

This what I initially thought, but after playing around with the commands, I think the source map should always be output to the file because the no-out flag says don't write contract program binary, and a source map isn't necessarily the binary.

But alternatively, we could also output the source map to stdout if the no output flag is raised. That sounds fine too, depending on how you interpret the no-out flag.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a fair point, and I can see the confusion. I can open up a PR to fix this.

That would be great, thanks!

I think the source map should always be output to the file because the no-out flag says don't write contract program binary, and a source map isn't necessarily the binary.

That's definitely a valid interpretation, but what do you name the source map file then? I think <input>.tok.map would work.

Though to be fair, I think it's quite rare to use the no-out flag, so failing in this case would not be that bad in my opinion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants